From: Shuanglei Tao Date: Sun, 28 Feb 2021 05:57:27 +0000 (+0800) Subject: protocol: add conpty support X-Git-Url: http://git.prime8.dev/?a=commitdiff_plain;h=d6921b235d1b97a57664f04e694b646f91fda5e6;p=ttyd.git protocol: add conpty support --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 85fc63c..d5663d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ else() set(CMAKE_C_STANDARD 99) endif() -set(SOURCE_FILES src/server.c src/http.c src/protocol.c src/utils.c) +set(SOURCE_FILES src/utils.c src/pty.c src/protocol.c src/http.c src/server.c) include(FindPackageHandleStandardArgs) diff --git a/src/protocol.c b/src/protocol.c index f548fe1..51b3df2 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -1,24 +1,12 @@ #include -#include -#include -#include -#include #include #include #include #include -#include -#include -#include - -#if defined(__OpenBSD__) || defined(__APPLE__) -#include -#elif defined(__FreeBSD__) -#include -#else -#include -#endif +#include +#include +#include "pty.h" #include "server.h" #include "utils.h" @@ -74,7 +62,7 @@ static bool check_host_origin(struct lws *wsi) { int origin_length = lws_hdr_total_length(wsi, WSI_TOKEN_ORIGIN); char buf[origin_length + 1]; memset(buf, 0, sizeof(buf)); - int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_ORIGIN); + int len = lws_hdr_copy(wsi, buf, (int) sizeof(buf), WSI_TOKEN_ORIGIN); if (len <= 0) { return false; } @@ -92,209 +80,70 @@ static bool check_host_origin(struct lws *wsi) { if (host_length != strlen(buf)) return false; char host_buf[host_length + 1]; memset(host_buf, 0, sizeof(host_buf)); - len = lws_hdr_copy(wsi, host_buf, sizeof(host_buf), WSI_TOKEN_HOST); + len = lws_hdr_copy(wsi, host_buf, (int) sizeof(host_buf), WSI_TOKEN_HOST); return len > 0 && strcasecmp(buf, host_buf) == 0; } -static bool fd_set_cloexec(const int fd) { - int flags = fcntl(fd, F_GETFD); - if (flags < 0) return false; - return (flags & FD_CLOEXEC) == 0 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1; -} - -static int fd_duplicate(int fd, uv_pipe_t *pipe) { - int fd_dup = dup(fd); - if (fd_dup < 0) return -errno; - - fd_set_cloexec(fd_dup); - - int status = uv_pipe_open(pipe, fd_dup); - if(status) close(fd_dup); - return status; -} - -static pid_t pty_fork(int *pty, const char *file, char *const argv[], const char *term) { - pid_t pid = forkpty(pty, NULL, NULL, NULL); - - if (pid < 0) { - return pid; - } else if (pid == 0) { - setenv("TERM", term, true); - int ret = execvp(file, argv); - if (ret < 0) { - perror("execvp failed\n"); - _exit(-errno); - } - } - - // set the file descriptor non blocking - int flags = fcntl(*pty, F_GETFL); - if (flags != -1) { - fcntl(*pty, F_SETFD, flags | O_NONBLOCK); - } - // set the file descriptor close-on-exec - fd_set_cloexec(*pty); - - return pid; -} - -static int pty_resize(int pty, int cols, int rows) { - struct winsize size; - - size.ws_col = (unsigned short)cols; - size.ws_row = (unsigned short)rows; - size.ws_xpixel = 0; - size.ws_ypixel = 0; - - return ioctl(pty, TIOCSWINSZ, &size); -} - -static void close_cb(uv_handle_t *handle) { - free(handle); -} - -static void pty_proc_free(struct pty_proc *proc) { - uv_read_stop((uv_stream_t *)proc->out_pipe); - if (proc->pty_fd >= 0) close(proc->pty_fd); - if (proc->pty_buffer != NULL) { - free(proc->pty_buffer); - proc->pty_buffer = NULL; - } - for (int i = 0; i < proc->argc; i++) { - free(proc->args[i]); - } - uv_close((uv_handle_t *)proc->in_pipe, close_cb); - uv_close((uv_handle_t *)proc->out_pipe, close_cb); - free(proc); -} - -static void alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { - buf->base = xmalloc(suggested_size); - buf->len = suggested_size; -} - -static void read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { - struct pss_tty *pss = (struct pss_tty *)stream->data; - struct pty_proc *proc = pss->proc; - proc->pty_len = nread; - - uv_read_stop(stream); - - if (nread == UV_ENOBUFS || nread == 0) return; - if (nread > 0) { - proc->pty_buffer = xmalloc(LWS_PRE + 1 + (size_t)nread); - memcpy(proc->pty_buffer + LWS_PRE + 1, buf->base, (size_t)nread); - } else { - proc->pty_buffer = NULL; - if (nread != UV_EOF) { - proc->err_count++; - lwsl_err("read_cb: %s (%s)\n", uv_err_name(nread), uv_strerror(nread)); - } - } - free(buf->base); - +static void process_read_cb(void *ctx, pty_buf_t *buf, bool eof) { + struct pss_tty *pss = (struct pss_tty *) ctx; + pss->pty_buf = buf; + pss->pty_eof = eof; lws_callback_on_writable(pss->wsi); } -static void child_cb(uv_signal_t *handle, int signum) { - pid_t pid; - 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 sig = WTERMSIG(stat); - char sig_name[20]; - - proc->status = 128 + sig; - get_sig_name(sig, sig_name, sizeof(sig_name)); - lwsl_notice("process killed with signal %d (%s), pid: %d\n", sig, sig_name, proc->pid); - } - - LIST_REMOVE(proc, entry); - if (proc->state == STATE_KILL) { - pty_proc_free(proc); - } else { - proc->state = STATE_EXIT; - } +static void process_exit_cb(void *ctx, pty_process *process) { + struct pss_tty *pss = (struct pss_tty *) ctx; + if (process->killed) { + lwsl_notice("process killed with signal %d, pid: %d\n", process->exit_signal, process->pid); + } else { + lwsl_notice("process exited with code %d, pid: %d\n", process->exit_code, process->pid); + pss->pty_eof = true; + lws_callback_on_writable(pss->wsi); } } -static int spawn_process(struct pss_tty *pss) { - struct pty_proc *proc = pss->proc; +static bool spawn_process(struct pss_tty *pss) { // append url args to arguments - char *argv[server->argc + proc->argc + 1]; + char *argv[server->argc + pss->argc + 1]; int i, n = 0; for (i = 0; i < server->argc; i++) { argv[n++] = server->argv[i]; } - for (i = 0; i < proc->argc; i++) { - argv[n++] = proc->args[i]; + for (i = 0; i < pss->argc; i++) { + argv[n++] = pss->args[i]; } argv[n] = NULL; - 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 - uv_disable_stdio_inheritance(); - int master, pid; - pid = pty_fork(&master, argv[0], argv, server->terminal_type); - if (pid < 0) { - lwsl_err("pty_fork: %d (%s)\n", errno, strerror(errno)); - return 1; + pty_process *process = process_init((void *) pss, server->loop, argv); + if (pty_spawn(process, process_read_cb, process_exit_cb) != 0) { + lwsl_err("pty_spawn: %d (%s)\n", errno, strerror(errno)); + process_free(process); + return false; } - - lwsl_notice("started process, pid: %d\n", pid); - - proc->pty_fd = master; - proc->pid = pid; - proc->out_pipe->data = pss; - if (fd_duplicate(master, proc->in_pipe) || fd_duplicate(master, proc->out_pipe)) - return 1; - + lwsl_notice("started process, pid: %d\n", process->pid); + pss->process = process; lws_callback_on_writable(pss->wsi); - return 0; + return true; } -static void kill_process(struct pty_proc *proc) { - if (proc->pid <= 0) return; - - pid_t pid = proc->pid; - int sig = server->sig_code; - char *sig_name = server->sig_name; - - lwsl_notice("killing process %d with signal: %d (%s)\n", pid, sig, sig_name); - int pgid = getpgid(pid); - if (uv_kill(pgid > 0 ? -pgid : pid, sig) != 0) { - lwsl_err("kill: %d, errno: %d (%s)\n", pid, errno, strerror(errno)); +static void wsi_output(struct lws *wsi, pty_buf_t *buf) { + if (buf == NULL) return; + char *wbuf = xmalloc(LWS_PRE + 1 + buf->len); + memcpy(wbuf + LWS_PRE + 1, buf->base, buf->len); + wbuf[LWS_PRE] = OUTPUT; + size_t n = buf->len + 1; + pty_buf_free(buf); + if (lws_write(wsi, (unsigned char *) wbuf + LWS_PRE, n, LWS_WRITE_BINARY) < n) { + lwsl_err("write OUTPUT to WS\n"); } -} - -static void write_cb(uv_write_t *req, int status) { - if (status != 0) lwsl_warn("uv_write callback returned status: %d\n", status); - free(req->data); - free(req); + free(wbuf); } 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; @@ -332,28 +181,18 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, pss->authenticated = false; pss->wsi = wsi; pss->buffer = NULL; - - pss->proc = proc = xmalloc(sizeof(struct pty_proc)); - memset(proc, 0, sizeof(struct pty_proc)); - proc->pty_fd = -1; - proc->status = -1; - proc->state = STATE_INIT; - proc->in_pipe = xmalloc(sizeof(uv_pipe_t)); - uv_pipe_init(server->loop, proc->in_pipe, 0); - proc->out_pipe = xmalloc(sizeof(uv_pipe_t)); - uv_pipe_init(server->loop, proc->out_pipe, 0); + pss->pty_buf = NULL; 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) { - proc->args = xrealloc(proc->args, (proc->argc + 1) * sizeof(char *)); - proc->args[proc->argc] = strdup(&buf[4]); - proc->argc++; + pss->args = xrealloc(pss->args, (pss->argc + 1) * sizeof(char *)); + pss->args[pss->argc] = strdup(&buf[4]); + pss->argc++; } } } - LIST_INSERT_HEAD(&server->procs, proc, entry); server->client_count++; #if LWS_LIBRARY_VERSION_NUMBER >= 2004000 @@ -367,11 +206,10 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, 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 *)proc->out_pipe, alloc_cb, read_cb); + pty_resume(pss->process); break; } if (send_initial_message(wsi, pss->initial_cmd_index) < 0) { @@ -385,25 +223,16 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, } // read error or client exited, close connection - if (proc->status == 0 || proc->pty_len == UV_EOF) { - lws_close_reason(wsi, LWS_CLOSE_STATUS_NORMAL, NULL, 0); + if (pss->pty_eof) { + lws_close_reason(wsi, pss->process->exit_code ? LWS_CLOSE_STATUS_UNEXPECTED_CONDITION : LWS_CLOSE_STATUS_NORMAL, NULL, 0); return 1; - } else if (proc->status > 0 || (proc->pty_len < 0 && proc->err_count == MAX_READ_RETRY)) { - lws_close_reason(wsi, LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, NULL, 0); - return -1; } - if (proc->pty_buffer != NULL && proc->pty_len > 0) { - 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(proc->pty_buffer); - proc->pty_buffer = NULL; + if (pss->pty_buf != NULL) { + wsi_output(wsi, pss->pty_buf); + pss->pty_buf = NULL; + pty_resume(pss->process); } - - uv_read_start((uv_stream_t *)proc->out_pipe, alloc_cb, read_cb); break; case LWS_CALLBACK_RECEIVE: @@ -430,20 +259,17 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, return 0; } - proc = pss->proc; switch (command) { case INPUT: - if (proc->pty_fd < 0) break; if (server->readonly) break; char *data = xmalloc(pss->len - 1); memcpy(data, pss->buffer + 1, pss->len - 1); - uv_buf_t b = {data, pss->len - 1}; uv_write_t *req = xmalloc(sizeof(uv_write_t)); req->data = data; - int err = uv_write(req, (uv_stream_t *)proc->in_pipe, &b, 1, write_cb); + int err = pty_write(pss->process, pty_buf_init(data, pss->len - 1)); if (err) { lwsl_err("uv_write: %s (%s)\n", uv_err_name(err), uv_strerror(err)); return -1; @@ -452,25 +278,19 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, case RESIZE_TERMINAL: { int cols, rows; if (parse_window_size(pss, &cols, &rows)) { - if (pty_resize(proc->pty_fd, cols, rows) < 0) { + if (pty_resize(pss->process, rows, cols) < 0) { lwsl_err("pty_resize: %d (%s)\n", errno, strerror(errno)); } } } break; case PAUSE: - if (proc->state == STATE_INIT) { - uv_read_stop((uv_stream_t *)proc->out_pipe); - proc->state = STATE_PAUSE; - } + pty_pause(pss->process); break; case RESUME: - if (proc->state == STATE_PAUSE) { - uv_read_start((uv_stream_t *)proc->out_pipe, alloc_cb, read_cb); - proc->state = STATE_INIT; - } + pty_resume(pss->process); break; case JSON_DATA: - if (proc->pid > 0) break; + if (pss->process != NULL) break; if (server->credential != NULL) { json_object *obj = json_tokener_parse(pss->buffer); struct json_object *o = NULL; @@ -486,7 +306,7 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, return -1; } } - if (spawn_process(pss) != 0) return 1; + if (!spawn_process(pss)) return 1; break; default: lwsl_warn("ignored unknown message type: %c\n", command); @@ -508,13 +328,10 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, 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->out_pipe); - kill_process(proc); + if (process_running(pss->process)) { + pty_pause(pss->process); + lwsl_notice("killing process, pid: %d\n", pss->process->pid); + pty_close(pss->process, server->sig_code); } if (server->once && server->client_count == 0) { diff --git a/src/pty.c b/src/pty.c new file mode 100644 index 0000000..cc2aa2d --- /dev/null +++ b/src/pty.c @@ -0,0 +1,459 @@ +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#if defined(__OpenBSD__) || defined(__APPLE__) +#include +#elif defined(__FreeBSD__) +#include +#else +#include +#endif +#endif + +#include "pty.h" +#include "utils.h" + +#ifdef _WIN32 +HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *); +HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); +void (WINAPI *pClosePseudoConsole)(HPCON); +#endif + +static void alloc_cb(uv_handle_t *unused, size_t suggested_size, uv_buf_t *buf) { + buf->base = xmalloc(suggested_size); + buf->len = suggested_size; +} + +static void close_cb(uv_handle_t *handle) { + free(handle); +} + +pty_buf_t *pty_buf_init(char *base, ssize_t len) { + pty_buf_t *buf = xmalloc(sizeof(pty_buf_t)); + buf->base = xmalloc(len); + memcpy(buf->base, base, len); + buf->len = len; + return buf; +} + +void pty_buf_free(pty_buf_t *buf) { + free(buf->base); + free(buf); +} + +static void pty_io_free(pty_io_t *io) { + uv_close((uv_handle_t *) io->in, close_cb); + uv_close((uv_handle_t *) io->out, close_cb); + free(io); +} + +static void read_cb(uv_stream_t *stream, ssize_t n, const uv_buf_t *buf) { + uv_read_stop(stream); + pty_io_t *io = (pty_io_t *) stream->data; + if (n <= 0) { + if (n == UV_ENOBUFS || n == 0) return; + io->read_cb(io->ctx, NULL, true); + goto done; + } + io->read_cb(io->ctx, pty_buf_init(buf->base, n), false); + +done: + free(buf->base); +} + +static void write_cb(uv_write_t *req, int unused) { + pty_buf_t *buf = (pty_buf_t *)req->data; + pty_buf_free(buf); + free(req); +} + +pty_process *process_init(void *ctx, uv_loop_t *loop, char **argv) { + pty_process *process = xmalloc(sizeof(pty_process)); + memset(process, 0, sizeof(pty_process)); + process->ctx = ctx; + process->loop = loop; + process->argv = argv; + process->exit_code = -1; + return process; +} + +bool process_running(pty_process *process) { + return process != NULL && process->pid > 0 && uv_kill(process->pid, 0) == 0; +} + +void process_free(pty_process *process) { + if (process == NULL) return; +#ifdef _WIN32 + if (process->si.lpAttributeList != NULL) { + DeleteProcThreadAttributeList(process->si.lpAttributeList); + free(process->si.lpAttributeList); + } + if (process->pty != NULL) pClosePseudoConsole(process->pty); + if (process->handle != NULL) CloseHandle(process->handle); +#else + uv_thread_join(&process->tid); +#endif + if (process->io != NULL) pty_io_free(process->io); + free(process); +} + +void pty_pause(pty_process *process) { + pty_io_t *io = process->io; + if (io->paused) return; + uv_read_stop((uv_stream_t *) io->out); +} + +void pty_resume(pty_process *process) { + pty_io_t *io = process->io; + if (!io->paused) return; + io->out->data = io; + uv_read_start((uv_stream_t *) io->out, alloc_cb, read_cb); +} + +int pty_write(pty_process *process, pty_buf_t *buf) { + pty_io_t *io = process->io; + uv_buf_t b = uv_buf_init(buf->base, buf->len); + uv_write_t *req = xmalloc(sizeof(uv_write_t)); + req->data = buf; + return uv_write(req, (uv_stream_t *) io->in, &b, 1, write_cb); +} + +int pty_resize(pty_process *process, int width, int height) { +#ifdef _WIN32 + COORD size = { height, width }; + return pResizePseudoConsole(process->pty, size) == S_OK ? 0 : -1; +#else + struct winsize size; + size.ws_col = (unsigned short) height; + size.ws_row = (unsigned short) width; + size.ws_xpixel = 0; + size.ws_ypixel = 0; + return ioctl(process->pty, TIOCSWINSZ, &size); +#endif +} + +int pty_close(pty_process *process, int sig) { + process->killed = true; +#ifdef _WIN32 + return TerminateProcess(process->handle, 1) ? 0 : 1; +#else + return uv_kill(-process->pid, sig); +#endif +} + +#ifdef _WIN32 +bool conpty_init() { + uv_lib_t kernel; + if (uv_dlopen("kernel32.dll", &kernel)) { + uv_dlclose(&kernel); + return false; + } + static struct { + char *name; + FARPROC *ptr; + } conpty_entry[] = { + { "CreatePseudoConsole", (FARPROC *)&pCreatePseudoConsole }, + { "ResizePseudoConsole", (FARPROC *)&pResizePseudoConsole }, + { "ClosePseudoConsole", (FARPROC *)&pClosePseudoConsole }, + { NULL, NULL } + }; + for (int i = 0; + conpty_entry[i].name != NULL && conpty_entry[i].ptr != NULL; i++) { + if (uv_dlsym(&kernel, conpty_entry[i].name, (void **)conpty_entry[i].ptr)) { + uv_dlclose(&kernel); + return false; + } + } + return true; +} + +// convert argv to cmdline for CreateProcessW +static WCHAR *join_args(char **argv) { + char *args = NULL; + for (; *argv; argv++) { + char *quoted = (char *) quote_arg(*argv); + size_t arg_len = args == NULL ? 1 : strlen(args) + 1; + size_t quoted_len = strlen(quoted); + args = xrealloc(args, arg_len + quoted_len); + if (arg_len == 1) memset(args, 0, 2); + if (arg_len != 1) strcat(args, " "); + strncat(args, quoted, quoted_len); + if (quoted != *argv) free(quoted); + } + + int len = MultiByteToWideChar(CP_UTF8, 0, args, -1, NULL, 0); + if (len <= 0) goto failed; + WCHAR *ws = (WCHAR*) xmalloc(len * sizeof(WCHAR)); + if (len != MultiByteToWideChar(CP_UTF8, 0, args, -1, ws, len)) { + free(ws); + goto failed; + } + return ws; + +failed: + if (args != NULL) free(args); + return NULL; +} + +static bool conpty_setup(HPCON *hnd, COORD size, STARTUPINFOEXW *si_ex, char **in_name, char **out_name) { + static int count = 0; + char buf[256]; + HPCON pty = INVALID_HANDLE_VALUE; + SECURITY_ATTRIBUTES sa = { 0 }; + HANDLE in_pipe = INVALID_HANDLE_VALUE; + HANDLE out_pipe = INVALID_HANDLE_VALUE; + const DWORD open_mode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE; + const DWORD pipe_mode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT; + DWORD pid = GetCurrentProcessId(); + bool ret = false; + + sa.nLength = sizeof(sa); + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\ttyd-term-in-%d-%d", pid, count); + *in_name = strdup(buf); + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\ttyd-term-out-%d-%d", pid, count); + *out_name = strdup(buf); + in_pipe = CreateNamedPipeA(*in_name, open_mode, pipe_mode, 1, 0, 0, 30000, &sa); + out_pipe = CreateNamedPipeA(*out_name, open_mode, pipe_mode, 1, 0, 0, 30000, &sa); + if (in_pipe == INVALID_HANDLE_VALUE || out_pipe == INVALID_HANDLE_VALUE) { + print_error("CreateNamedPipeA"); + goto failed; + } + + HRESULT hr = pCreatePseudoConsole(size, in_pipe, out_pipe, 0, &pty); + if (FAILED(hr)) { + print_error("CreatePseudoConsole"); + goto failed; + } + + si_ex->StartupInfo.cb = sizeof(STARTUPINFOEXW); + si_ex->StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + si_ex->StartupInfo.hStdError = NULL; + si_ex->StartupInfo.hStdInput = NULL; + si_ex->StartupInfo.hStdOutput = NULL; + size_t bytes_required; + InitializeProcThreadAttributeList(NULL, 1, 0, &bytes_required); + si_ex->lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST) xmalloc(bytes_required); + if (!InitializeProcThreadAttributeList(si_ex->lpAttributeList, 1, 0, &bytes_required)) { + print_error("InitializeProcThreadAttributeList"); + goto failed; + } + if (!UpdateProcThreadAttribute(si_ex->lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + pty, sizeof(HPCON), NULL, NULL)) { + print_error("UpdateProcThreadAttribute"); + goto failed; + } + count++; + *hnd = pty; + ret = true; + goto done; + +failed: + ret = false; + free(*in_name); + *in_name = NULL; + free(*out_name); + *out_name = NULL; +done: + if (in_pipe != INVALID_HANDLE_VALUE) CloseHandle(in_pipe); + if (out_pipe != INVALID_HANDLE_VALUE) CloseHandle(out_pipe); + return ret; +} + +static void connect_cb(uv_connect_t *req, int status) { + free(req); +} + +static void CALLBACK conpty_exit(void *context, BOOLEAN unused) { + pty_process *process = (pty_process *) context; + uv_async_send(&process->async); +} + +static void async_cb(uv_async_t *async) { + pty_process *process = (pty_process *) async->data; + UnregisterWait(process->wait); + + DWORD exit_code; + GetExitCodeProcess(process->handle, &exit_code); + process->exit_code = (int) exit_code; + process->exit_signal = 1; + process->exit_cb(process->ctx, process); + uv_close((uv_handle_t *) async, NULL); + process_free(process); +} + +int pty_spawn(pty_process *process, pty_read_cb read_cb, pty_exit_cb exit_cb) { + WCHAR *cmdline = NULL; + char *in_name = NULL; + char *out_name = NULL; + DWORD flags = EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT; + COORD size = { 80, 24 }; + + if (!conpty_setup(&process->pty, size, &process->si, &in_name, &out_name)) return 1; + + SetConsoleCtrlHandler(NULL, FALSE); + + int status = 1; + pty_io_t *io = xmalloc(sizeof(pty_io_t)); + io->in = xmalloc(sizeof(uv_pipe_t)); + io->out = xmalloc(sizeof(uv_pipe_t)); + uv_pipe_init(process->loop, io->in, 0); + uv_pipe_init(process->loop, io->out, 0); + io->paused = true; + io->read_cb = read_cb; + io->ctx = process->ctx; + process->io = io; + + uv_connect_t *in_req = xmalloc(sizeof(uv_connect_t)); + uv_connect_t *out_req = xmalloc(sizeof(uv_connect_t)); + uv_pipe_connect(in_req, io->in, in_name, connect_cb); + uv_pipe_connect(out_req, io->out, out_name, connect_cb); + + PROCESS_INFORMATION pi = { 0 }; + cmdline = join_args(process->argv); + if (cmdline == NULL) goto cleanup; + + if (!CreateProcessW(NULL, cmdline, NULL, NULL, FALSE, flags, NULL, NULL, &process->si.StartupInfo, &pi)) { + print_error("CreateProcessW"); + goto cleanup; + } + + process->pid = GetProcessId(pi.hProcess); + process->handle = pi.hProcess; + + process->exit_cb = exit_cb; + process->async.data = process; + uv_async_init(process->loop, &process->async, async_cb); + + if(!RegisterWaitForSingleObject(&process->wait, pi.hProcess, conpty_exit, process, INFINITE, WT_EXECUTEONLYONCE)) { + print_error("RegisterWaitForSingleObject"); + goto cleanup; + } + + status = 0; + +cleanup: + if (in_name != NULL) free(in_name); + if (out_name != NULL) free(out_name); + if (cmdline != NULL) free(cmdline); + + return status; +} +#else +static bool fd_set_cloexec(const int fd) { + int flags = fcntl(fd, F_GETFD); + if (flags < 0) return false; + return (flags & FD_CLOEXEC) == 0 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1; +} + +static bool fd_duplicate(int fd, uv_pipe_t *pipe) { + int fd_dup = dup(fd); + if (fd_dup < 0) return false; + + if (!fd_set_cloexec(fd_dup)) return false; + + int status = uv_pipe_open(pipe, fd_dup); + if(status) close(fd_dup); + return status == 0; +} + +static void wait_cb(void *arg) { + pty_process *process = (pty_process *) arg; + + pid_t pid; + int stat; + do + pid = waitpid(process->pid, &stat, 0); + while (pid != process->pid && errno == EINTR); + + if (WIFEXITED(stat)) { + process->exit_code = WEXITSTATUS(stat); + } + if (WIFSIGNALED(stat)) { + int sig = WTERMSIG(stat); + process->exit_code = 128 + sig; + process->exit_signal = sig; + } + + uv_async_send(&process->async); +} + +static void async_cb(uv_async_t *async) { + pty_process *process = (pty_process *) async->data; + process->exit_cb(process->ctx, process); + uv_close((uv_handle_t *) async, NULL); + process_free(process); +} + +int pty_spawn(pty_process *process, pty_read_cb read_cb, pty_exit_cb exit_cb) { + int status = 0; + + uv_disable_stdio_inheritance(); + + int master, pid; + pid = forkpty(&master, NULL, NULL, NULL); + if (pid < 0) { + status = -errno; + goto error; + } else if (pid == 0) { + setsid(); + int ret = execvp(process->argv[0], process->argv); + if (ret < 0) { + perror("execvp failed\n"); + _exit(-errno); + } + } + + int flags = fcntl(master, F_GETFL); + if (flags == -1) { + status = -errno; + goto error; + } + if(fcntl(master, F_SETFD, flags | O_NONBLOCK) == -1) { + status = -errno; + goto error; + } + if (!fd_set_cloexec(master)) { + status=-errno; + goto error; + } + + pty_io_t *io = xmalloc(sizeof(pty_io_t)); + io->in = xmalloc(sizeof(uv_pipe_t)); + io->out = xmalloc(sizeof(uv_pipe_t)); + uv_pipe_init(process->loop, io->in, 0); + uv_pipe_init(process->loop, io->out, 0); + io->paused = true; + io->read_cb = read_cb; + io->ctx = process->ctx; + if (!fd_duplicate(master, io->in) || !fd_duplicate(master, io->out)) { + status = -errno; + pty_io_free(io); + goto error; + } + + process->pty = master; + process->pid = pid; + process->io = io; + + process->exit_cb = exit_cb; + process->async.data = process; + uv_async_init(process->loop, &process->async, async_cb); + uv_thread_create(&process->tid, wait_cb, process); + + return 0; + +error: + close(master); + uv_kill(pid, SIGKILL); + waitpid(pid, NULL, 0); + return status; +} +#endif \ No newline at end of file diff --git a/src/pty.h b/src/pty.h new file mode 100644 index 0000000..08b9993 --- /dev/null +++ b/src/pty.h @@ -0,0 +1,71 @@ +#ifndef TTYD_PTY_H +#define TTYD_PTY_H + +#include +#include + +#ifdef _WIN32 +#ifndef HPCON +# define HPCON VOID * +#endif +#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE +# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016 +#endif + +bool conpty_init(); +#endif + +typedef struct { + char *base; + ssize_t len; +} pty_buf_t; + +typedef void (*pty_read_cb)(void *, pty_buf_t *, bool); + +typedef struct { + uv_pipe_t *in; + uv_pipe_t *out; + bool paused; + + pty_read_cb read_cb; + void *ctx; +} pty_io_t; + +struct pty_process_; +typedef struct pty_process_ pty_process; +typedef void (*pty_exit_cb)(void *, pty_process *); + +struct pty_process_ { + int pid, exit_code, exit_signal; + bool killed; +#ifdef _WIN32 + STARTUPINFOEXW si; + HPCON pty; + HANDLE handle; + HANDLE wait; +#else + pid_t pty; + uv_thread_t tid; +#endif + char **argv; + + uv_loop_t *loop; + uv_async_t async; + pty_io_t *io; + pty_exit_cb exit_cb; + void *ctx; +}; + +pty_buf_t *pty_buf_init(char *base, ssize_t len); +void pty_buf_free(pty_buf_t *buf); +pty_process *process_init(void *ctx, uv_loop_t *loop, char **argv); +bool process_running(pty_process *process); +void process_free(pty_process *process); +int pty_spawn(pty_process *process, pty_read_cb read_cb, pty_exit_cb exit_cb); +void pty_pause(pty_process *process); +void pty_resume(pty_process *process); +int pty_write(pty_process *process, pty_buf_t *buf); +int pty_resize(pty_process *process, int width, int height); +int pty_close(pty_process *process, int sig); + +#endif // TTYD_PTY_H diff --git a/src/queue.h b/src/queue.h deleted file mode 100644 index daf4553..0000000 --- a/src/queue.h +++ /dev/null @@ -1,574 +0,0 @@ -/* - * 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 d2b1e97..23e6099 100644 --- a/src/server.c +++ b/src/server.c @@ -144,7 +144,6 @@ static struct server *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"); @@ -167,7 +166,8 @@ static struct server *server_new(int argc, char **argv, int start) { ts->command = xmalloc(cmd_len + 1); char *ptr = ts->command; for (int i = 0; i < cmd_argc; i++) { - ptr = stpcpy(ptr, ts->argv[i]); + size_t len = strlen(ts->argv[i]); + ptr = memcpy (ptr, ts->argv[i], len + 1) + len; if (i != cmd_argc - 1) { *ptr++ = ' '; } @@ -176,8 +176,6 @@ static struct server *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; } @@ -199,8 +197,6 @@ static void server_free(struct server *ts) { unlink(ts->socket_path); } } - uv_signal_stop(&ts->watcher); - uv_close((uv_handle_t *)&server->watcher, NULL); uv_loop_close(ts->loop); free(ts->loop); free(ts); @@ -275,6 +271,12 @@ int main(int argc, char **argv) { print_help(); return 0; } +#ifdef _WIN32 + if (!conpty_init()) { + fprintf(stderr, "ERROR: ConPTY init failed! Make sure you are on Windows 10 1809 or later."); + return 1; + } +#endif int start = calc_command_start(argc, argv); server = server_new(argc, argv, start); @@ -444,7 +446,7 @@ int main(int argc, char **argv) { case 't': optind--; for (; optind < start && *argv[optind] != '-'; optind++) { - char *option = strdup(optarg); + char *option = optarg; char *key = strsep(&option, "="); if (key == NULL) { fprintf(stderr, @@ -459,7 +461,6 @@ int main(int argc, char **argv) { optarg); return -1; } - free(option); struct json_object *obj = json_tokener_parse(value); json_object_object_add( client_prefs, key, diff --git a/src/server.h b/src/server.h index 4ea753d..5d76103 100644 --- a/src/server.h +++ b/src/server.h @@ -1,7 +1,7 @@ #include #include -#include "queue.h" +#include "pty.h" // client message #define INPUT '0' @@ -30,8 +30,6 @@ extern struct lws_context *context; extern struct server *server; extern struct endpoints endpoints; -typedef enum { STATE_INIT, STATE_PAUSE, STATE_KILL, STATE_EXIT } proc_state; - struct pss_http { char path[128]; char *buffer; @@ -39,37 +37,22 @@ struct pss_http { size_t len; }; -struct pty_proc { - char **args; - int argc; - - pid_t pid; - int status; - proc_state state; - - int pty_fd; - char *pty_buffer; - ssize_t pty_len; - int err_count; - - uv_pipe_t *in_pipe; - uv_pipe_t *out_pipe; - - LIST_ENTRY(pty_proc) entry; -}; - struct pss_tty { bool initialized; int initial_cmd_index; bool authenticated; char address[50]; char path[128]; + char **args; + int argc; struct lws *wsi; char *buffer; size_t len; - struct pty_proc *proc; + pty_process *process; + pty_buf_t *pty_buf; + bool pty_eof; }; struct server { @@ -91,7 +74,4 @@ struct server { 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 }; diff --git a/src/utils.c b/src/utils.c index 7443884..17d8cca 100644 --- a/src/utils.c +++ b/src/utils.c @@ -18,9 +18,8 @@ const char *sys_signame[NSIG] = { #if defined(_WIN32) || defined(__CYGWIN__) #include // https://github.com/mirror/newlib-cygwin/blob/master/winsup/cygwin/strsig.cc -#ifndef NSIG +#undef NSIG #define NSIG 33 -#endif const char *sys_signame[NSIG] = { "zero", "HUP", "INT", "QUIT", "ILL", "TRAP", "IOT", "EMT", "FPE", "KILL", "BUS", "SEGV", "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", @@ -79,7 +78,7 @@ int open_uri(char *uri) { sprintf(command, "open %s > /dev/null 2>&1", uri); return system(command); #elif defined(_WIN32) || defined(__CYGWIN__) - return ShellExecute(0, 0, uri, 0, 0, SW_SHOW) > 32 ? 0 : 1; + return ShellExecute(0, 0, uri, 0, 0, SW_SHOW) > (HINSTANCE) 32 ? 0 : 1; #else // check if X server is running if (system("xset -q > /dev/null 2>&1")) return 1; @@ -114,3 +113,86 @@ char *base64_encode(const unsigned char *buffer, size_t length) { return ret; } + +#ifdef _WIN32 +char *strsep(char **sp, char *sep) { + char *p, *s; + if (sp == NULL || *sp == NULL || **sp == '\0') return(NULL); + s = *sp; + p = s + strcspn(s, sep); + if (*p != '\0') *p++ = '\0'; + *sp = p; + return(s); +} + +// https://github.com/git/git/blob/306ee63a703ad67c54ba1209dc11dd9ea500dc1f/compat/mingw.c#L1111 +const char *quote_arg(const char *arg) { + int len = 0, n = 0; + int force_quotes = 0; + char *q, *d; + const char *p = arg; + if (!*p) force_quotes = 1; + while (*p) { + if (isspace(*p) || *p == '*' || *p == '?' || *p == '{' || *p == '\'') + force_quotes = 1; + else if (*p == '"') + n++; + else if (*p == '\\') { + int count = 0; + while (*p == '\\') { + count++; + p++; + len++; + } + if (*p == '"' || !*p) + n += count*2 + 1; + continue; + } + len++; + p++; + } + if (!force_quotes && n == 0) + return arg; + + d = q = xmalloc(len + n + 3); + *d++ = '"'; + while (*arg) { + if (*arg == '"') + *d++ = '\\'; + else if (*arg == '\\') { + int count = 0; + while (*arg == '\\') { + count++; + *d++ = *arg++; + } + if (*arg == '"' || !*arg) { + while (count-- > 0) + *d++ = '\\'; + if (!*arg) + break; + *d++ = '\\'; + } + } + *d++ = *arg++; + } + *d++ = '"'; + *d++ = '\0'; + return q; +} + +void print_error(char *func) { + LPVOID buffer; + DWORD dw = GetLastError(); + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &buffer, + 0, NULL ); + wprintf(L"== %s failed with error %d: %s", func, dw, buffer); + LocalFree(buffer); +} +#endif diff --git a/src/utils.h b/src/utils.h index eb51bdf..85aee46 100644 --- a/src/utils.h +++ b/src/utils.h @@ -31,4 +31,9 @@ int open_uri(char *uri); // Encode text to base64, the caller should free the returned string char *base64_encode(const unsigned char *buffer, size_t length); +#ifdef _WIN32 +char *strsep(char **sp, char *sep); +const char *quote_arg(const char *arg); +void print_error(char *func); +#endif #endif // TTYD_UTIL_H