]> prime8.dev >> repos - ttyd.git/commitdiff
protocol: add conpty support
authorShuanglei Tao <tsl0922@gmail.com>
Sun, 28 Feb 2021 05:57:27 +0000 (13:57 +0800)
committerShuanglei Tao <tsl0922@gmail.com>
Wed, 3 Mar 2021 13:02:33 +0000 (21:02 +0800)
CMakeLists.txt
src/protocol.c
src/pty.c [new file with mode: 0644]
src/pty.h [new file with mode: 0644]
src/queue.h [deleted file]
src/server.c
src/server.h
src/utils.c
src/utils.h

index 85fc63c05f0493f824c21b5b07a2a7c38a731bc2..d5663d5bf4445d896126293da77b036d4a87eeef 100644 (file)
@@ -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)
 
index f548fe119544c5c99f4005d1e798347c21c5a26f..51b3df2b6821efadf5c48417830e7e79347a5769 100644 (file)
@@ -1,24 +1,12 @@
 #include <errno.h>
-#include <fcntl.h>
-#include <json.h>
-#include <libwebsockets.h>
-#include <signal.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/ioctl.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#if defined(__OpenBSD__) || defined(__APPLE__)
-#include <util.h>
-#elif defined(__FreeBSD__)
-#include <libutil.h>
-#else
-#include <pty.h>
-#endif
+#include <json.h>
+#include <libwebsockets.h>
 
+#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 (file)
index 0000000..cc2aa2d
--- /dev/null
+++ b/src/pty.c
@@ -0,0 +1,459 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef _WIN32
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#if defined(__OpenBSD__) || defined(__APPLE__)
+#include <util.h>
+#elif defined(__FreeBSD__)
+#include <libutil.h>
+#else
+#include <pty.h>
+#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 (file)
index 0000000..08b9993
--- /dev/null
+++ b/src/pty.h
@@ -0,0 +1,71 @@
+#ifndef TTYD_PTY_H
+#define TTYD_PTY_H
+
+#include <stdbool.h>
+#include <uv.h>
+
+#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 (file)
index daf4553..0000000
+++ /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 */
index d2b1e97ab7a5110a7eb8140dcc763fb40f3b06db..23e60999517fe35e26f6a348dfa180639d8a8d20 100644 (file)
@@ -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,
index 4ea753d884bb03343bf4e91c9cb360ea9b3d6200..5d761039b5408204c8517c704258add68266b208 100644 (file)
@@ -1,7 +1,7 @@
 #include <stdbool.h>
 #include <uv.h>
 
-#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
 };
index 7443884eed73908d1d4daa4a2b24ca8140fbd8f0..17d8ccaa1001e7632ca34702535d339d4a9a8d4b 100644 (file)
@@ -18,9 +18,8 @@ const char *sys_signame[NSIG] = {
 #if defined(_WIN32) || defined(__CYGWIN__)
 #include <windows.h>
 // 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
index eb51bdf672736955276c77be57497e70bbff8ff7..85aee4685a9e6112a4232a7bce4f20e76f3b7a37 100644 (file)
@@ -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