mirror of
https://github.com/tsl0922/ttyd.git
synced 2025-12-29 06:54:35 +01:00
protocol: add conpty support
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
303
src/protocol.c
303
src/protocol.c
@@ -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) {
|
||||
|
||||
459
src/pty.c
Normal file
459
src/pty.c
Normal file
@@ -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
|
||||
71
src/pty.h
Normal file
71
src/pty.h
Normal file
@@ -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
|
||||
574
src/queue.h
574
src/queue.h
@@ -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 */
|
||||
17
src/server.c
17
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,
|
||||
|
||||
32
src/server.h
32
src/server.h
@@ -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
|
||||
};
|
||||
|
||||
88
src/utils.c
88
src/utils.c
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user