diff --git a/docs/build.rst b/docs/build.rst index 3c741ebd9..912bb047d 100644 --- a/docs/build.rst +++ b/docs/build.rst @@ -23,10 +23,6 @@ following dependencies are installed first. should use a version of the source that matches the binary version as closely as possible, since the two are tightly coupled. - You may need to unset the :envvar:`KITTY_PREWARM_SOCKET` environment variable, - otherwise your modified code will not be executed, when running some of the - ``kitty +`` and ``kitty @`` commands. - Dependencies ---------------- diff --git a/docs/changelog.rst b/docs/changelog.rst index c5ccfc460..10b975f3f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -40,7 +40,7 @@ Detailed list of changes - A new option :opt:`remote_control_password` to use fine grained permissions for what can be remote controlled (:disc:`5320`) -- Reduce startup latency by ~30 milliseconds when running kittens and remote control commands inside kitty (:iss:`5159`) +- Reduce startup latency by ~30 milliseconds when running kittens via key bindings inside kitty (:iss:`5159`) - A new option :opt:`modify_font` to adjust various font metrics like underlines, cell sizes etc. (:pull:`5265`) diff --git a/docs/glossary.rst b/docs/glossary.rst index 8a8e09cf6..c084f052f 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -142,17 +142,6 @@ Variables that kitty sets when running child programs A public key that programs can use to communicate securely with kitty using the remote control protocol. The format is: :code:`protocol:key data`. -.. envvar:: KITTY_PREWARM_SOCKET - - Path to a UNIX domain socket used to avoid Python interpreter startup - latency when running kittens, or remote control or using ``kitty +launch`` or - ``kitty +runpy``. - -.. envvar:: KITTY_PREWARM_SOCKET_REAL_TTY - - Path to the the PTY used to run the prewarmed process in when using - :envvar:`KITTY_PREWARM_SOCKET`. - .. envvar:: WINDOWID The id for the :term:`OS Window ` the program is running in. Only available diff --git a/gen-config.py b/gen-config.py index 813684c66..d87a75d71 100755 --- a/gen-config.py +++ b/gen-config.py @@ -2,9 +2,7 @@ # License: GPLv3 Copyright: 2021, Kovid Goyal -import os import re -import sys from typing import List from kitty.conf.generate import write_output @@ -24,9 +22,6 @@ def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ') def main() -> None: - if 'prewarmed' in getattr(sys, 'kitty_run_data'): - os.environ.pop('KITTY_PREWARM_SOCKET') - os.execlp(sys.executable, sys.executable, '+launch', __file__, *sys.argv[1:]) from kitty.options.definition import definition write_output('kitty', definition) nullable_colors = [] diff --git a/kitty/child.py b/kitty/child.py index 8b9cb6882..b8095619f 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -148,7 +148,6 @@ def process_env() -> Dict[str, str]: ssl_env_var = getattr(sys, 'kitty_ssl_env_var', None) if ssl_env_var is not None: ans.pop(ssl_env_var, None) - ans.pop('KITTY_PREWARM_SOCKET', None) return ans @@ -256,9 +255,6 @@ class Child: env['KITTY_LISTEN_ON'] = boss.listening_on else: env.pop('KITTY_LISTEN_ON', None) - if not self.is_prewarmed: - env['KITTY_PREWARM_SOCKET'] = boss.prewarm.socket_env_var() - env['KITTY_PREWARM_SOCKET_REAL_TTY'] = ' ' * 32 if self.cwd: # needed in case cwd is a symlink, in which case shells # can use it to display the current directory name rather @@ -464,12 +460,7 @@ class Child: import termios if self.child_fd is None: return False - tty_name = self.foreground_environ.get('KITTY_PREWARM_SOCKET_REAL_TTY') - if tty_name and tty_name.startswith('/'): - with open(os.open(tty_name, os.O_RDWR | os.O_CLOEXEC | os.O_NOCTTY, 0)) as real_tty: - t = termios.tcgetattr(real_tty.fileno()) - else: - t = termios.tcgetattr(self.child_fd) + t = termios.tcgetattr(self.child_fd) if not t[3] & termios.ISIG: return False cc = t[-1] diff --git a/kitty/launcher/main.c b/kitty/launcher/main.c index b24f5a73f..6ecc0ed22 100644 --- a/kitty/launcher/main.c +++ b/kitty/launcher/main.c @@ -268,11 +268,9 @@ read_exe_path(char *exe, size_t buf_sz) { } #endif // }}} -extern void use_prewarmed_process(int argc, char *argv[], char *envp[]); int main(int argc, char *argv[], char* envp[]) { if (argc < 1 || !argv) { fprintf(stderr, "Invalid argc/argv\n"); return 1; } - use_prewarmed_process(argc, argv, envp); char exe[PATH_MAX+1] = {0}; char exe_dir_buf[PATH_MAX+1] = {0}; FREE_AFTER_FUNCTION const char *lc_ctype = NULL; diff --git a/kitty/launcher/prewarm.c b/kitty/launcher/prewarm.c deleted file mode 100644 index 78aefc44b..000000000 --- a/kitty/launcher/prewarm.c +++ /dev/null @@ -1,827 +0,0 @@ -/* - * Copyright (C) 2022 Kovid Goyal - * - * Distributed under terms of the GPL3 license. - */ - -// for SA_RESTART -#define _XOPEN_SOURCE 700 -// for cfmakeraw -#ifdef __APPLE__ -#define _DARWIN_C_SOURCE 1 -const static int has_splice = 0; -#else -#define _DEFAULT_SOURCE -// for splice() -#define _GNU_SOURCE -#ifdef __linux__ -#define HAS_SPLICE -const static int has_splice = 0; // sadly the kernel doesnt support splice between tty fds anymore - // https://www.spinics.net/lists/kernel/msg3799785.html -#else -const static int has_splice = 0; -#endif -#endif - -// Includes {{{ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __APPLE__ -#include -#include -#include -#include -#else -#include -#endif -#include - -#define arraysz(x) (sizeof(x)/sizeof(x[0])) - -#define MAX(x, y) __extension__ ({ \ - __typeof__ (x) a = (x); __typeof__ (y) b = (y); \ - a > b ? a : b;}) -#define MIN(x, y) __extension__ ({ \ - __typeof__ (x) a = (x); __typeof__ (y) b = (y); \ - a < b ? a : b;}) -// }}} - -#define IO_BUZ_SZ 8192 -#define remove_i_from_array(array, i, count) { \ - (count)--; \ - if ((i) < (count)) { \ - memmove((array) + (i), (array) + (i) + 1, sizeof((array)[0]) * ((count) - (i))); \ - }} - -// ancient macs (10.15) have no MSG_NOSIGNAL -#ifndef MSG_NOSIGNAL -# define MSG_NOSIGNAL 0 -# ifdef SO_NOSIGPIPE -# define KITTY_USE_SO_NOSIGPIPE -# else -# error "Cannot block SIGPIPE!" -# endif -#endif - -typedef struct transfer_buf { - char *buf; - size_t sz; -} transfer_buf; -static transfer_buf from_child_tty = {0}; -static transfer_buf to_child_tty = {0}; -static char child_tty_name[256]; -static int child_master_fd = -1, child_slave_fd = -1; -static struct winsize self_winsize = {0}; -static struct termios self_termios = {0}, restore_termios = {0}; -static bool termios_needs_restore = false; -static int self_ttyfd = -1, socket_fd = -1, signal_read_fd = -1, signal_write_fd = -1; -static int stdin_pos = -1, stdout_pos = -1, stderr_pos = -1; -static char fd_send_buf[256]; -struct iovec launch_msg = {0}; -struct msghdr launch_msg_container = {.msg_control = fd_send_buf, .msg_controllen = sizeof(fd_send_buf), .msg_iov = &launch_msg, .msg_iovlen = 1 }; -static size_t launch_msg_cap = 0; -char *launch_msg_buf = NULL; -static pid_t child_pid = 0; - - -static void -left_shift_buffer(transfer_buf *t, size_t n) { - if (t->sz > n) { - t->sz -= n; - memmove(t->buf, t->buf + n, t->sz); - } else t->sz = 0; -} - -#define err_prefix "prewarm wrapper process error: " -static inline void -print_error(const char *s, int errnum) { - if (errnum != 0) fprintf(stderr, "%s%s: %s\n\r", err_prefix, s, strerror(errnum)); - else fprintf(stderr, "%s%s\n\r", err_prefix, s); -} -#define pe(fmt, ...) { fprintf(stderr, err_prefix); fprintf(stderr, fmt, __VA_ARGS__); fprintf(stderr, "\n\r"); } - -static bool -parse_long(const char *str, long *val) { - char *temp; - bool rc = true; - errno = 0; - const long t = strtol(str, &temp, 0); - if (temp == str || *temp != '\0' || ((*val == LONG_MIN || *val == LONG_MAX) && errno == ERANGE)) rc = false; - *val = t; - return rc; -} - -static inline int -safe_open(const char *path, int flags, mode_t mode) { - while (true) { - int fd = open(path, flags, mode); - if (fd == -1 && errno == EINTR) continue; - return fd; - } -} - -static inline void -safe_close(int fd) { - while(close(fd) != 0 && errno == EINTR); -} - -static inline int -safe_dup2(int a, int b) { - int ret; - while((ret = dup2(a, b)) < 0 && errno == EINTR); - return ret; -} - -static inline bool -safe_tcsetattr(int fd, int actions, const struct termios *tp) { - int ret = 0; - while((ret = tcsetattr(fd, actions, tp)) != 0 && errno == EINTR); - return ret == 0; -} - -static ssize_t -safe_read(int fd, void *buf, size_t n) { - ssize_t ret = 0; - while((ret = read(fd, buf, n)) ==-1 && errno == EINTR); - return ret; -} - -static ssize_t -safe_send(int fd, void *buf, size_t n, int flags) { - ssize_t ret = 0; - while((ret = send(fd, buf, n, flags)) ==-1 && errno == EINTR); - return ret; -} - - -static ssize_t -safe_write(int fd, void *buf, size_t n) { - ssize_t ret = 0; - while((ret = write(fd, buf, n)) ==-1 && errno == EINTR); - return ret; -} - -static bool -set_blocking(int fd, bool blocking) { - if (fd < 0) return false; - int flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) return false; - flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); - return (fcntl(fd, F_SETFL, flags) == 0) ? true : false; -} - -static int -connect_to_socket_synchronously(const char *addr) { - struct sockaddr_un sock_addr = {.sun_family=AF_UNIX}; - strncpy(sock_addr.sun_path, addr, sizeof(sock_addr.sun_path) - 1); - int fd = socket(AF_UNIX, SOCK_STREAM, 0); -#ifdef __linux__ - const size_t addrlen = strnlen(sock_addr.sun_path, sizeof(sock_addr.sun_path)) + sizeof(sock_addr.sun_family); - if (sock_addr.sun_path[0] == '@') sock_addr.sun_path[0] = 0; -#else - const size_t addrlen = sizeof(sock_addr); -#endif - if (connect(fd, (struct sockaddr*)&sock_addr, addrlen) != 0) { - if (errno != EINTR && errno != EINPROGRESS) return -1; - struct pollfd poll_data = {.fd=fd, .events=POLLOUT}; - while (poll (&poll_data, 1, -1) == -1) { if (errno != EINTR) return -1; } - int socket_error_code = 0; - socklen_t sizeof_socket_error_code = sizeof(socket_error_code); - if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &socket_error_code, &sizeof_socket_error_code) == -1) return -1; - if (socket_error_code != 0) return -1; - } - return fd; -} - -static bool -is_prewarmable(int argc, char *argv[]) { - if (argc < 2) return false; - switch (argv[1][0]) { - case '+': - if (argv[1][1] != 0) return strcmp(argv[1], "+open") != 0; - if (argc < 3) return false; - return strcmp(argv[2], "open") != 0; - break; - case '@': - return true; - break; - default: - return false; - break; - } - return false; -} - -static bool -get_termios_state(struct termios *t) { - while (tcgetattr(self_ttyfd, t) != 0) { - if (errno != EINTR) return false; - } - return true; -} - -static void -restore_termios_settings(void) { - if (self_ttyfd > -1 && termios_needs_restore) { - // we only restore termios if its current state is the same as the result of cfmakeraw() on the startup termios state - // this is to try to detect if something like less changed the termios state. This allows, for instance kitty @ ls | less to work - struct termios current_state; - if (get_termios_state(¤t_state) && memcmp(&self_termios, ¤t_state, sizeof(current_state)) == 0) { - safe_tcsetattr(self_ttyfd, TCSAFLUSH, &restore_termios); - termios_needs_restore = false; - } - } -} - -static void -reapply_termios_settings(void) { - safe_tcsetattr(self_ttyfd, TCSANOW, &self_termios); - termios_needs_restore = true; -} - -static void -cleanup(void) { - child_pid = 0; - restore_termios_settings(); -#define cfd(fd) if (fd > -1) { safe_close(fd); fd = -1; } - cfd(child_master_fd); cfd(child_slave_fd); - cfd(self_ttyfd); cfd(socket_fd); cfd(signal_read_fd); cfd(signal_write_fd); -#undef cfd - if (launch_msg_buf) { free(launch_msg_buf); launch_msg.iov_len = 0; launch_msg_buf = NULL; } - if (from_child_tty.buf) { free(from_child_tty.buf); from_child_tty.buf = NULL; } -} - -static bool -safe_winsz(int fd, int action, struct winsize *ws) { - int ret; - while ((ret = ioctl(fd, action, ws)) == -1 && errno == EINTR); - return ret != -1; -} - -static bool -get_window_size(void) { - return safe_winsz(self_ttyfd, TIOCGWINSZ, &self_winsize); -} - -bool -set_iutf8(int fd, bool on) { - (void)fd; (void)on; -#ifdef IUTF8 - struct termios attrs; - if (tcgetattr(fd, &attrs) != 0) return false; - if (on) attrs.c_iflag |= IUTF8; - else attrs.c_iflag &= ~IUTF8; - if (tcsetattr(fd, TCSANOW, &attrs) != 0) return false; -#endif - return true; -} - - -static bool -open_pty(void) { - while (openpty(&child_master_fd, &child_slave_fd, child_tty_name, &self_termios, &self_winsize) == -1) { - if (errno != EINTR) return false; - } - return set_iutf8(child_master_fd, true); -} - -static void -handle_signal(int sig_num, siginfo_t *si, void *ucontext) { - (void)sig_num; (void)ucontext; - int save_err = errno; - char *buf = (char*)si; - size_t sz = sizeof(siginfo_t); - while (signal_write_fd != -1 && sz) { - // as long as sz is less than PIPE_BUF write will either write all or return -1 with EAGAIN - // so we are guaranteed atomic writes, barring implementation bugs - ssize_t ret = safe_write(signal_write_fd, buf, sz); - if (ret <= 0) break; - sz -= ret; - buf += ret; - } - errno = save_err; -} - -static bool -setup_signal_handler(void) { - int fds[2]; - if (pipe(fds) != 0) return false; - signal_read_fd = fds[0]; signal_write_fd = fds[1]; - set_blocking(signal_write_fd, false); - sigset_t masked_signals; - sigemptyset(&masked_signals); - sigaddset(&masked_signals, SIGWINCH); - sigaddset(&masked_signals, SIGINT); - sigaddset(&masked_signals, SIGTERM); - sigaddset(&masked_signals, SIGQUIT); - sigaddset(&masked_signals, SIGHUP); - sigaddset(&masked_signals, SIGTSTP); - struct sigaction act = {.sa_sigaction=handle_signal, .sa_flags=SA_SIGINFO | SA_RESTART, .sa_mask = masked_signals}; -#define a(which) if (sigaction(which, &act, NULL) != 0) return false; - a(SIGWINCH); a(SIGINT); a(SIGTERM); a(SIGQUIT); a(SIGHUP); a(SIGTSTP); -#undef a - return true; -} - -static void -setup_stdio_handles(void) { - int pos = 0; - if (!isatty(STDIN_FILENO)) stdin_pos = pos++; - if (!isatty(STDOUT_FILENO)) { - stdout_pos = pos++; - } - if (!isatty(STDERR_FILENO)) stderr_pos = pos++; -} - -static bool -ensure_launch_msg_space(size_t sz) { - if (launch_msg_cap > launch_msg.iov_len + sz) return true; - const size_t c = MAX(2 * launch_msg_cap, launch_msg_cap + launch_msg.iov_len + sz + 8); - launch_msg_cap = MAX(c, 64 * 1024); - launch_msg_buf = realloc(launch_msg_buf, launch_msg_cap); - return launch_msg_buf != NULL; -} - -static bool -write_item_to_launch_msg(const char *prefix, const char *data) { - size_t prefixlen = strlen(prefix), datalen = strlen(data), msg_len = 8 + prefixlen + datalen; - if (!ensure_launch_msg_space(msg_len)) return false; - memcpy(launch_msg_buf + launch_msg.iov_len, prefix, prefixlen); - launch_msg.iov_len += prefixlen; - launch_msg_buf[launch_msg.iov_len++] = ':'; - memcpy(launch_msg_buf + launch_msg.iov_len, data, datalen); - launch_msg.iov_len += datalen; - launch_msg_buf[launch_msg.iov_len++] = 0; - launch_msg.iov_base = launch_msg_buf; - return true; -} - -extern char **environ; - -static bool -create_launch_msg(int argc, char *argv[]) { -#define w(prefix, data) { if (!write_item_to_launch_msg(prefix, data)) return false; } - static char buf[4*PATH_MAX]; - w("tty_name", child_tty_name); - snprintf(buf, sizeof(buf), "%zu", sizeof(self_winsize)); - w("winsize", buf); - if (getcwd(buf, sizeof(buf))) { w("cwd", buf); } - for (int i = 0; i < argc; i++) w("argv", argv[i]); - char **s = environ; - for (; *s; s++) w("env", *s); - int num_fds = 0, fds[4]; -#define sio(which, x) if (which##_pos > -1) { snprintf(buf, sizeof(buf), "%d", which##_pos); w(#which, buf); fds[num_fds++] = x; } - sio(stdin, STDIN_FILENO); sio(stdout, STDOUT_FILENO); sio(stderr, STDERR_FILENO); -#undef sio - w("finish", ""); - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&launch_msg_container); - cmsg->cmsg_len = CMSG_LEN(sizeof(fds[0]) * num_fds); - memcpy(CMSG_DATA(cmsg), fds, num_fds * sizeof(fds[0])); - launch_msg_container.msg_controllen = cmsg->cmsg_len; - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - return true; -#undef w -} - -static int exit_status = EXIT_FAILURE; -static int pending_signals[32] = {0}; -enum ChildState { CHILD_NOT_STARTED, CHILD_STARTED, CHILD_STOPPED, CHILD_EXITED }; -static enum ChildState child_state = CHILD_NOT_STARTED; -static void flush_data(void); - -static bool -read_from_zygote(void) { - ssize_t n; - static char buf[64]; - static transfer_buf t = {.buf=buf}; - n = safe_read(socket_fd, t.buf + t.sz, sizeof(buf) - t.sz); - if (n < 0) { - if (errno == EIO || errno == EPIPE) { socket_fd = -1; return true; } - return false; - } - if (n) { - t.sz += n; - while (t.sz >= sizeof(long long)) { - long long val = *((long long*)t.buf); - left_shift_buffer(&t, sizeof(long long)); - if (child_state == CHILD_NOT_STARTED) { - if (val == 0) { print_error("Got zero child pid from prewarm socket", 0); return false; } - child_pid = val; - child_state = CHILD_STARTED; - if (child_slave_fd > -1) { safe_close(child_slave_fd); child_slave_fd = -1; } - for (size_t i = 0; i < arraysz(pending_signals) && pending_signals[i]; i++) { - kill(child_pid, pending_signals[i]); - } - memset(pending_signals, 0, sizeof(pending_signals)); - } else { - int child_exit_status = val; - if (WIFEXITED(child_exit_status)) { - exit_status = WEXITSTATUS(child_exit_status); - child_pid = 0; - child_state = CHILD_EXITED; - } else if (WIFSIGNALED(child_exit_status)) { - int signum = WTERMSIG(child_exit_status); - if (signum > 0) { - flush_data(); - cleanup(); - signal(signum, SIG_DFL); - raise(signum); - _exit(1); - } - } else if (WIFSTOPPED(child_exit_status)) { - child_state = CHILD_STOPPED; - int signum = WSTOPSIG(child_exit_status); - restore_termios_settings(); - struct sigaction defval = {.sa_handler = SIG_DFL}, original; - sigaction(signum, &defval, &original); - raise(signum); - sigaction(signum, &original, NULL); - reapply_termios_settings(); - if (child_pid > 0) { - kill(child_pid, SIGCONT); - } - child_state = CHILD_STARTED; - } - } - } - } else { socket_fd = -1; return true; } - return true; -} - -static void -close_sent_fds(void) { -#define redirect(which, mode) { int fd = safe_open("/dev/null", mode | O_CLOEXEC, 0); if (fd > -1) { safe_dup2(fd, which); safe_close(fd); } } - if (stdin_pos > -1) redirect(STDIN_FILENO, O_RDONLY); - if (stdout_pos > -1) redirect(STDOUT_FILENO, O_WRONLY); - if (stderr_pos > -1) redirect(STDERR_FILENO, O_WRONLY); -#undef redirect -} - -static bool -send_launch_msg(void) { - ssize_t n; - while ((n = sendmsg(socket_fd, &launch_msg_container, MSG_NOSIGNAL)) < 0 && errno == EINTR); - if (n < 0) return false; - if (n == 0) { errno = EPIPE; return false; } - // some bytes sent, null out the control msg data as it is already sent - launch_msg_container.msg_controllen = 0; - launch_msg_container.msg_control = NULL; - if ((size_t)n > launch_msg.iov_len) { - launch_msg.iov_len = 0; - close_sent_fds(); - } - else launch_msg.iov_len -= n; - launch_msg.iov_base = (char*)launch_msg.iov_base + n; - return true; -} - -struct fd_to_watch { - bool want_read, want_write, want_error; -}; - -struct watched_fds { - struct fd_to_watch self_ttyfd, signal_read_fd, socket_fd, child_master_fd; -}; -static struct watched_fds wf = {0}; - -static bool -read_from_tty(int *fd, transfer_buf *t) { - if (*fd < 0) return true; - if (t->sz < IO_BUZ_SZ) { - ssize_t n = safe_read(*fd, t->buf + t->sz, IO_BUZ_SZ - t->sz); - if (n < 0) { - if (errno == EPIPE || errno == EIO) { *fd = -1; return true; } - if (errno == EAGAIN) return true; - return false; - } - if (n == 0) *fd = -1; // hangup - t->sz += n; - } - return true; -} - -static bool -read_from_child_tty(void) { - return read_from_tty(&child_master_fd, &from_child_tty); -} - -static bool -write_to_tty(transfer_buf *src, int *dest_fd) { - if (*dest_fd < 0) return true; - if (src->sz) { - ssize_t n = safe_write(*dest_fd, src->buf, src->sz); - if (n < 0) { - if (errno == EPIPE || errno == EIO) { *dest_fd = -1; return true; } - return false; - } - if (n > 0) { - left_shift_buffer(src, n); - } else *dest_fd = -1; - } - return true; -} - -static bool -from_child_to_self(void) { - return write_to_tty(&from_child_tty, &self_ttyfd); -} - -static bool -from_self_to_child(void) { - return write_to_tty(&to_child_tty, &child_master_fd); -} - - -static bool -read_from_self_tty(void) { - return read_from_tty(&self_ttyfd, &to_child_tty); -} - -static bool window_size_dirty = false; - -static bool -read_signals(void) { - static char buf[sizeof(siginfo_t) * 8]; - static transfer_buf b = {.buf=buf}; - ssize_t len = safe_read(signal_read_fd, buf + b.sz, sizeof(buf) - b.sz); - if (len < 0) return false; - if (len == 0) return true; - b.sz += len; - while (b.sz >= sizeof(siginfo_t)) { - siginfo_t *sig = (siginfo_t*)buf; - switch(sig->si_signo) { - case SIGWINCH: - window_size_dirty = true; break; - case SIGINT: case SIGTERM: case SIGHUP: case SIGQUIT: case SIGTSTP: - if (child_pid > 0) kill(child_pid, sig->si_signo); - else { - for (size_t i = 0; i < arraysz(pending_signals); i++) { - if (!pending_signals[i]) { - pending_signals[i] = sig->si_signo; - break; - } - } - } - break; - } - left_shift_buffer(&b, sizeof(siginfo_t)); - } - return true; -} - -static bool -keep_going(void) { - switch(child_state) { - case CHILD_NOT_STARTED: - return self_ttyfd > -1 && signal_read_fd > -1 && socket_fd > -1 && child_master_fd > -1; - case CHILD_STARTED: - case CHILD_STOPPED: - return self_ttyfd > -1 && signal_read_fd > -1 && socket_fd > -1; - case CHILD_EXITED: - return self_ttyfd > -1 && signal_read_fd > -1 && child_master_fd > -1; - } - return false; -} - -static void -flush_data(void) { - if (self_ttyfd > -1 && from_child_tty.sz > 0) { - set_blocking(self_ttyfd, false); - from_child_to_self(); - } - - if (child_master_fd > -1 && from_child_tty.sz < IO_BUZ_SZ) { - set_blocking(child_master_fd, false); - read_from_child_tty(); - } - - if (self_ttyfd > -1 && from_child_tty.sz > 0) { - from_child_to_self(); - } -} - -static char sosbuf[2 * sizeof(self_winsize)] = {0}; -static transfer_buf send_on_socket = {.buf=sosbuf}; - -static void -add_window_size_to_buffer(void) { - char *p; - if (send_on_socket.sz % sizeof(self_winsize)) { - // partial send - if (send_on_socket.sz > sizeof(self_winsize)) send_on_socket.sz -= sizeof(self_winsize); // replace second size - p = send_on_socket.buf + send_on_socket.sz; - send_on_socket.sz += sizeof(self_winsize); - } else { - // replace all sizes - p = send_on_socket.buf; - send_on_socket.sz = sizeof(self_winsize); - } - memcpy(p, &self_winsize, sizeof(self_winsize)); -} - -static bool -send_over_socket(void) { - if (!send_on_socket.sz || socket_fd < 0) return true; - ssize_t n = safe_send(socket_fd, send_on_socket.buf, send_on_socket.sz, MSG_NOSIGNAL); - if (n < 0) return false; - if (n) { - if (n >= send_on_socket.sz) send_on_socket.sz = 0; - else { - left_shift_buffer(&send_on_socket, n); - } - } - return true; -} - -static void -set_append_mode(int fd, bool yes) { - int val = fcntl(fd, F_GETFL); - if (yes) val |= O_APPEND; else val &= ~O_APPEND; - fcntl(fd, F_SETFL, val); -} - -#ifndef HAS_SPLICE -#define splice(...) -1 -#endif - -static bool -transfer_in_kernel(int from, int to, bool *read_failed) { - if (has_splice) { - ssize_t n = 0; - while ((n = splice(from, NULL, to, NULL, PIPE_BUF, 0)) == -1 && errno == EINTR); - *read_failed = n == 0; - return n > 0; - } - return false; -} - -static void -loop(void) { -#define fail(s) { print_error(s, errno); return; } - int ret, nfds = 0; -#define init(which) wf.which.want_read = true; nfds = MAX(which, nfds); - init(self_ttyfd); init(signal_read_fd); init(socket_fd); init(child_master_fd); -#undef init - fd_set readable, writable, errorable; - nfds++; - struct { bool data_available_from_child, data_available_from_self; } sd; - if (has_splice) { set_append_mode(child_master_fd, false); set_append_mode(self_ttyfd, false); } - - while (keep_going()) { - if (window_size_dirty) { - if (!get_window_size()) fail("getting window size for self tty failed"); - // macOS barfs with ENOTTY if we try to use TIOCSWINSZ from this process, so send it to the zygote - /* if (!safe_winsz(child_master_fd, TIOCSWINSZ, &self_winsize)) fail("setting window size on child pty failed"); */ - add_window_size_to_buffer(); - window_size_dirty = false; - } - const bool can_splice = has_splice && child_master_fd > -1 && self_ttyfd > -1; - static bool splice_ok = true; - if (can_splice && splice_ok) { - wf.self_ttyfd.want_read = !sd.data_available_from_self; - wf.self_ttyfd.want_write = sd.data_available_from_child; - wf.child_master_fd.want_read = !sd.data_available_from_child; - wf.child_master_fd.want_write = sd.data_available_from_self; - if (!splice_ok) { set_append_mode(self_ttyfd, true); set_append_mode(child_master_fd, true); } - } else { - wf.self_ttyfd.want_read = to_child_tty.sz < IO_BUZ_SZ; wf.self_ttyfd.want_write = from_child_tty.sz > 0; - wf.child_master_fd.want_read = from_child_tty.sz < IO_BUZ_SZ; wf.child_master_fd.want_write = to_child_tty.sz > 0; - } - wf.socket_fd.want_write = launch_msg.iov_len > 0 || send_on_socket.sz > 0; - - FD_ZERO(&readable); FD_ZERO(&writable); FD_ZERO(&errorable); -#define set(which) if (which > -1) { if (wf.which.want_read) { FD_SET(which, &readable); } if (wf.which.want_write) { FD_SET(which, &writable); } if (wf.which.want_error) { FD_SET(which, &errorable); } } - set(self_ttyfd); set(child_master_fd); set(socket_fd); set(signal_read_fd); -#undef set - while ((ret = select(nfds, &readable, &writable, &errorable, NULL)) == -1) { if (errno != EINTR) fail("select() failed"); } - if (!ret) continue; - - if (can_splice && splice_ok) { - bool read_failed = false; - if (FD_ISSET(self_ttyfd, &readable)) sd.data_available_from_self = true; - if (sd.data_available_from_self && FD_ISSET(child_master_fd, &writable)) { - splice_ok = transfer_in_kernel(self_ttyfd, child_master_fd, &read_failed); - sd.data_available_from_self = false; - if (read_failed) fail("reading in kernel from self tty failed"); - } - if (FD_ISSET(child_master_fd, &readable)) sd.data_available_from_child = true; - if (sd.data_available_from_child && FD_ISSET(self_ttyfd, &writable) && splice_ok) { - splice_ok = transfer_in_kernel(child_master_fd, self_ttyfd, &read_failed); - sd.data_available_from_child = false; - if (read_failed) fail("reading in kernel from child tty failed"); - } - } else { - if (child_master_fd > -1) { - if (FD_ISSET(child_master_fd, &writable)) if (!from_self_to_child()) fail("writing to child tty failed"); - if (FD_ISSET(child_master_fd, &readable)) { - if (!read_from_child_tty()) fail("reading from child tty failed"); - } - } - if (self_ttyfd > -1) { - if (FD_ISSET(self_ttyfd, &readable)) if (!read_from_self_tty()) fail("reading from self tty failed"); - if (FD_ISSET(self_ttyfd, &writable)) if (!from_child_to_self()) fail("writing to self tty failed"); - } - } - - if (signal_read_fd > -1 && FD_ISSET(signal_read_fd, &readable)) if (!read_signals()) fail("reading from signal fd failed"); - - if (socket_fd > -1) { - if (FD_ISSET(socket_fd, &writable)) { - if (launch_msg.iov_len > 0) { if (!send_launch_msg()) fail("sending launch message failed"); } - else if (send_on_socket.sz > 0) { if (!send_over_socket()) fail("sending on socket failed"); } - } - if (FD_ISSET(socket_fd, &readable)) { - if (!read_from_zygote()) fail("reading information about child failed"); - if (socket_fd < 0) { // hangup - child_pid = 0; - child_state = CHILD_EXITED; - } - } - } - } -#undef fail -} - -static char* -check_socket_addr(char *addr) { - char *p = strchr(addr, ':'); - if (!p) return NULL; - *p = 0; - long val = -1; - bool ok = parse_long(addr, &val); - *p = ':'; - if (!ok || val != geteuid()) return NULL; - addr = p + 1; - p = strchr(addr, ':'); - if (!p) return NULL; - *p = 0; - ok = parse_long(addr, &val); - *p = ':'; - if (!ok || val != getegid()) return NULL; - return p + 1; -} - -void -use_prewarmed_process(int argc, char *argv[], char *envp[]) { - char *env_addr = getenv("KITTY_PREWARM_SOCKET"); - if (!env_addr || !*env_addr || !is_prewarmable(argc, argv)) return; - env_addr = check_socket_addr(env_addr); - if (!env_addr) return; - self_ttyfd = safe_open(ctermid(NULL), O_RDWR | O_NONBLOCK, 0); - if (self_ttyfd < 0) return; - setup_stdio_handles(); -#define fail(s) { print_error(s, errno); cleanup(); return; } - if (!setup_signal_handler()) fail("Failed to setup signal handling"); - if (!get_window_size()) fail("Failed to get window size of controlling terminal"); - if (!get_termios_state(&self_termios)) fail("Failed to get termios state of controlling terminal"); - if (!open_pty()) fail("Failed to open slave pty"); - memcpy(&restore_termios, &self_termios, sizeof(restore_termios)); - termios_needs_restore = true; - cfmakeraw(&self_termios); - if (!safe_tcsetattr(self_ttyfd, TCSANOW, &self_termios)) fail("Failed to put tty into raw mode"); - if (!create_launch_msg(argc, argv)) fail("Failed to create launch message"); - socket_fd = connect_to_socket_synchronously(env_addr); - if (socket_fd < 0) fail("Failed to connect to prewarm socket"); -#ifdef KITTY_USE_SO_NOSIGPIPE - { - int val = 1; - if (setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&val, sizeof(val)) != 0) fail("Failed to set SO_NOSIGPIPE"); - } -#endif - from_child_tty.buf = malloc(IO_BUZ_SZ * 2); - if (!from_child_tty.buf) fail("Out of memory allocating IO buffer"); - to_child_tty.buf = from_child_tty.buf + IO_BUZ_SZ; -#undef fail - - while (*envp) { - char *p = *envp; - const char *q = "KITTY_PREWARM_SOCKET_REAL_TTY="; - while (*p && *q && *p == *q) { - if (*p == '=') { - p++; - snprintf(p, strlen(p) + 1, "%s", child_tty_name); - break; - } - p++; q++; - } - envp++; - } - loop(); - flush_data(); - cleanup(); - exit(exit_status); -} diff --git a/kitty/prewarm.py b/kitty/prewarm.py index 2ba954e9c..d6b6bba65 100644 --- a/kitty/prewarm.py +++ b/kitty/prewarm.py @@ -1,15 +1,12 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2022, Kovid Goyal -import errno import fcntl import io import json import os import select import signal -import socket -import struct import sys import termios import time @@ -27,14 +24,14 @@ from typing import ( from kitty.constants import kitty_exe, running_in_kitty from kitty.entry_points import main as main_entry_point from kitty.fast_data_types import ( - CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, getpeereid, - install_signal_handlers, read_signals, remove_signal_handlers, safe_pipe, - set_options, set_use_os_log + CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, install_signal_handlers, + read_signals, remove_signal_handlers, safe_pipe, set_options, + set_use_os_log ) from kitty.options.types import Options from kitty.shm import SharedMemory from kitty.types import SignalInfo -from kitty.utils import log_error, random_unix_socket, safer_fork +from kitty.utils import log_error, safer_fork if TYPE_CHECKING: from _typeshed import ReadableBuffer, WriteableBuffer @@ -90,7 +87,6 @@ class PrewarmProcess: to_prewarm_stdin: int, from_prewarm_stdout: int, from_prewarm_death_notify: int, - unix_socket_name: str, ) -> None: self.children: Dict[int, Child] = {} self.worker_pid = prewarm_process_pid @@ -99,10 +95,6 @@ class PrewarmProcess: self.read_from_process_fd = from_prewarm_stdout self.poll = select.poll() self.poll.register(self.read_from_process_fd, select.POLLIN) - self.unix_socket_name = unix_socket_name - - def socket_env_var(self) -> str: - return f'{os.geteuid()}:{os.getegid()}:{self.unix_socket_name}' def take_from_worker_fd(self, create_file: bool = False) -> int: if create_file: @@ -375,20 +367,6 @@ def fork(shm_address: str, free_non_child_resources: Callable[[], None]) -> Tupl return 0, -1 # type: ignore -def verify_socket_creds(conn: socket.socket) -> bool: - # needed as abstract unix sockets used on Linux have no permissions and - # older BSDs ignore socket file permissions - uid, gid = getpeereid(conn.fileno()) - return uid == os.geteuid() and gid == os.getegid() - - -class SocketChildData: - def __init__(self) -> None: - self.cwd = self.tty_name = '' - self.argv: List[str] = [] - self.env: Dict[str, str] = {} - - Funtion = TypeVar('Funtion', bound=Callable[..., Any]) @@ -421,218 +399,7 @@ interactive_and_job_control_signals = ( ) -def fork_socket_child(child_data: SocketChildData, tty_fd: int, stdio_fds: Dict[str, int], free_non_child_resources: Callable[[], None]) -> int: - # see https://www.gnu.org/software/libc/manual/html_node/Launching-Jobs.html - child_pid = safer_fork() - if child_pid: - return child_pid - # child process - eintr_retry(os.setpgid)(0, 0) - eintr_retry(os.tcsetpgrp)(tty_fd, eintr_retry(os.getpgid)(0)) - for x in interactive_and_job_control_signals: - signal.signal(x, signal.SIG_DFL) - restore_python_signal_handlers() - # the std streams fds are closed in free_non_child_resources() - for which in ('stdin', 'stdout', 'stderr'): - fd = stdio_fds[which] if stdio_fds[which] > -1 else tty_fd - safe_dup2(fd, getattr(sys, which).fileno()) - free_non_child_resources() - child_main({'cwd': child_data.cwd, 'env': child_data.env, 'argv': child_data.argv}, prewarm_type='socket') - - -def fork_socket_child_supervisor(conn: socket.socket, free_non_child_resources: Callable[[], None]) -> None: - import array - global is_zygote - if safer_fork(): - conn.close() - return - is_zygote = False - os.setsid() - restore_python_signal_handlers() - free_non_child_resources() - signal_read_fd = install_signal_handlers(signal.SIGCHLD, signal.SIGUSR1)[0] - # See https://www.gnu.org/software/libc/manual/html_node/Initializing-the-Shell.html - for x in interactive_and_job_control_signals: - signal.signal(x, signal.SIG_IGN) - poll = select.poll() - poll.register(signal_read_fd, select.POLLIN) - from_socket_buf = b'' - to_socket_buf = b'' - keep_going = True - child_pid = -1 - socket_fd = conn.fileno() - launch_msg_read = False - os.set_blocking(socket_fd, False) - received_fds: List[int] = [] - stdio_positions = dict.fromkeys(('stdin', 'stdout', 'stderr'), -1) - stdio_fds = dict.fromkeys(('stdin', 'stdout', 'stderr'), -1) - winsize = 8 - exit_after_write = False - child_data = SocketChildData() - - def handle_signal(siginfo: SignalInfo) -> None: - nonlocal to_socket_buf, exit_after_write, child_pid - if siginfo.si_signo != signal.SIGCHLD or siginfo.si_code not in (CLD_KILLED, CLD_EXITED, CLD_STOPPED): - return - while True: - try: - pid, status = os.waitpid(-1, os.WNOHANG | os.WUNTRACED) - except ChildProcessError: - pid = 0 - if not pid: - break - if pid != child_pid: - continue - to_socket_buf += struct.pack('q', status) - if not os.WIFSTOPPED(status): - exit_after_write = True - child_pid = -1 - - def write_to_socket() -> None: - nonlocal keep_going, to_socket_buf, keep_going - buf = memoryview(to_socket_buf) - while buf: - try: - n = os.write(socket_fd, buf) - except OSError: - n = 0 - if n == 0: - keep_going = False - return - buf = buf[n:] - to_socket_buf = bytes(buf) - if exit_after_write and not to_socket_buf: - keep_going = False - - def read_winsize() -> None: - nonlocal from_socket_buf - msg = conn.recv(io.DEFAULT_BUFFER_SIZE) - if not msg: - return - from_socket_buf += msg - data = memoryview(from_socket_buf) - record = memoryview(b'') - while len(data) >= winsize: - record, data = data[:winsize], data[winsize:] - if record: - try: - with open(safe_open(os.ctermid(), os.O_RDWR | os.O_CLOEXEC), 'w') as f: - safe_ioctl(f.fileno(), termios.TIOCSWINSZ, record) - except OSError: - traceback.print_exc() - from_socket_buf = bytes(data) - - def read_launch_msg() -> bool: - nonlocal keep_going, from_socket_buf, launch_msg_read, winsize - try: - msg, ancdata, flags, addr = conn.recvmsg(io.DEFAULT_BUFFER_SIZE, 1024) - except OSError as e: - if e.errno == errno.ENOMEM: - # macOS does this when no ancilliary data is present - msg, ancdata, flags, addr = conn.recvmsg(io.DEFAULT_BUFFER_SIZE) - else: - raise - - for cmsg_level, cmsg_type, cmsg_data in ancdata: - if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS: - fds = array.array("i") # Array of ints - # Append data, ignoring any truncated integers at the end. - fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) - received_fds.extend(fds) - - if not msg: - return False - from_socket_buf += msg - while (idx := from_socket_buf.find(b'\0')) > -1: - line = from_socket_buf[:idx].decode('utf-8') - from_socket_buf = from_socket_buf[idx+1:] - cmd, _, payload = line.partition(':') - if cmd == 'finish': - for x in received_fds: - os.set_inheritable(x, True) - os.set_blocking(x, True) - for k, pos in stdio_positions.items(): - if pos > -1: - stdio_fds[k] = received_fds[pos] - del received_fds[:] - return True - elif cmd == 'cwd': - child_data.cwd = payload - elif cmd == 'env': - k, _, v = payload.partition('=') - child_data.env[k] = v - elif cmd == 'argv': - child_data.argv.append(payload) - elif cmd in stdio_positions: - stdio_positions[cmd] = int(payload) - elif cmd == 'tty_name': - child_data.tty_name = payload - elif cmd == 'winsize': - winsize = int(payload) - return False - - def free_non_child_resources2() -> None: - for fd in received_fds: - safe_close(fd) - for k, v in tuple(stdio_fds.items()): - if v > -1: - safe_close(v) - stdio_fds[k] = -1 - conn.close() - - def launch_child() -> None: - nonlocal to_socket_buf, child_pid - sys.__stdout__.flush() - sys.__stderr__.flush() - tty_fd = establish_controlling_tty(child_data.tty_name, closefd=False) - child_pid = fork_socket_child(child_data, tty_fd, stdio_fds, free_non_child_resources2) - if child_pid: - # this is also done in the child process, but we dont - # know when, so do it here as well - eintr_retry(os.setpgid)(child_pid, child_pid) - eintr_retry(os.tcsetpgrp)(tty_fd, child_pid) - for fd in stdio_fds.values(): - if fd > -1: - safe_close(fd) - safe_close(tty_fd) - else: - raise SystemExit('fork_socket_child() returned in the child process') - to_socket_buf += struct.pack('q', child_pid) - - def read_from_socket() -> None: - nonlocal launch_msg_read - if launch_msg_read: - read_winsize() - else: - if read_launch_msg(): - launch_msg_read = True - launch_child() - - try: - while keep_going: - poll.register(socket_fd, select.POLLIN | (select.POLLOUT if to_socket_buf else 0)) - for fd, event in poll.poll(): - if event & error_events: - keep_going = False - break - if fd == socket_fd: - if event & select.POLLOUT: - write_to_socket() - if event & select.POLLIN: - read_from_socket() - elif fd == signal_read_fd and event & select.POLLIN: - read_signals(signal_read_fd, handle_signal) - finally: - if child_pid: # supervisor process - with suppress(OSError): - conn.shutdown(socket.SHUT_RDWR) - with suppress(OSError): - conn.close() - - raise SystemExit(0) - - -def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket: socket.socket) -> None: +def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int) -> None: global parent_tty_name with suppress(OSError): parent_tty_name = os.ttyname(sys.stdout.fileno()) @@ -640,12 +407,9 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket: os.set_blocking(stdin_fd, False) os.set_blocking(stdout_fd, False) signal_read_fd = install_signal_handlers(signal.SIGCHLD, signal.SIGUSR1)[0] - os.set_blocking(unix_socket.fileno(), False) - unix_socket.listen(5) poll = select.poll() poll.register(stdin_fd, select.POLLIN) poll.register(signal_read_fd, select.POLLIN) - poll.register(unix_socket.fileno(), select.POLLIN) input_buf = output_buf = child_death_buf = b'' child_ready_fds: Dict[int, int] = {} child_pid_map: Dict[int, int] = {} @@ -666,7 +430,6 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket: for fd in get_all_non_child_fds(): if fd > -1: safe_close(fd) - unix_socket.close() def check_event(event: int, err_msg: str) -> None: if event & select.POLLHUP: @@ -766,15 +529,6 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket: read_signals(signal_read_fd, handle_signal) - def handle_socket_client(event: int) -> None: - check_event(event, 'UNIX socket fd listener failed') - conn, addr = unix_socket.accept() - if not verify_socket_creds(conn): - print_error('Connection attempted with invalid credentials ignoring') - conn.close() - return - fork_socket_child_supervisor(conn, free_non_child_resources) - keep_type_checker_happy = True try: while is_zygote and keep_type_checker_happy: @@ -791,8 +545,6 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket: handle_signals(event) elif q == notify_child_death_fd: handle_notify_child_death(event) - elif q == unix_socket.fileno(): - handle_socket_client(event) except (KeyboardInterrupt, EOFError, BrokenPipeError): if is_zygote: raise SystemExit(1) @@ -809,33 +561,18 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket: safe_close(fmd) -def get_socket_name(unix_socket: socket.socket) -> str: - sname = unix_socket.getsockname() - if isinstance(sname, bytes): - sname = sname.decode('utf-8') - assert isinstance(sname, str) - if sname.startswith('\0'): - sname = '@' + sname[1:] - return sname - - -def exec_main(stdin_read: int, stdout_write: int, death_notify_write: int, unix_socket: Optional[socket.socket] = None) -> None: +def exec_main(stdin_read: int, stdout_write: int, death_notify_write: int) -> None: os.setsid() os.set_inheritable(stdin_read, False) os.set_inheritable(stdout_write, False) os.set_inheritable(death_notify_write, False) running_in_kitty(False) - if unix_socket is None: - unix_socket = random_unix_socket() - os.write(stdout_write, f'{get_socket_name(unix_socket)}\n'.encode('utf-8')) if not sys.stdout.line_buffering: # happens if the parent kitty instance has stdout not pointing to a terminal sys.stdout.reconfigure(line_buffering=True) # type: ignore try: - main(stdin_read, stdout_write, death_notify_write, unix_socket) + main(stdin_read, stdout_write, death_notify_write) finally: set_options(None) - if is_zygote: - unix_socket.close() def fork_prewarm_process(opts: Options, use_exec: bool = False) -> Optional[PrewarmProcess]: @@ -850,21 +587,15 @@ def fork_prewarm_process(opts: Options, use_exec: bool = False) -> Optional[Prew child_pid = tp.pid tp.returncode = 0 # prevent a warning when the popen object is deleted with the process still running os.set_blocking(stdout_read, True) - with open(stdout_read, 'rb', closefd=False) as f: - socket_name = f.readline().decode('utf-8').rstrip() os.set_blocking(stdout_read, False) else: - unix_socket = random_unix_socket() - socket_name = get_socket_name(unix_socket) child_pid = safer_fork() if child_pid: # master - if not use_exec: - unix_socket.close() safe_close(stdin_read) safe_close(stdout_write) safe_close(death_notify_write) - p = PrewarmProcess(child_pid, stdin_write, stdout_read, death_notify_read, socket_name) + p = PrewarmProcess(child_pid, stdin_write, stdout_read, death_notify_read) if use_exec: p.reload_kitty_config() return p @@ -874,5 +605,5 @@ def fork_prewarm_process(opts: Options, use_exec: bool = False) -> Optional[Prew safe_close(stdout_read) safe_close(death_notify_read) set_options(opts) - exec_main(stdin_read, stdout_write, death_notify_write, unix_socket) + exec_main(stdin_read, stdout_write, death_notify_write) raise SystemExit(0) diff --git a/kitty_tests/prewarm.py b/kitty_tests/prewarm.py index a098c11fb..bed9362bf 100644 --- a/kitty_tests/prewarm.py +++ b/kitty_tests/prewarm.py @@ -9,9 +9,8 @@ import signal import subprocess import tempfile import time -from contextlib import suppress -from kitty.constants import kitty_exe, terminfo_dir +from kitty.constants import kitty_exe from kitty.fast_data_types import ( CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, has_sigqueue, install_signal_handlers, read_signals, sigqueue @@ -24,139 +23,6 @@ class Prewarm(BaseTest): maxDiff = None - def test_socket_prewarming(self): - from kitty.prewarm import fork_prewarm_process, wait_for_child_death - exit_code = 17 - src = '''\ -def socket_child_main(exit_code=0, initial_print=''): - import os, sys, json, signal - from kitty.fast_data_types import get_options - from kitty.utils import read_screen_size - - def report_screen_size_change(*a): - print("Screen size changed:", read_screen_size(fd=sys.stderr.fileno()).cols, file=sys.stderr, flush=True) - - def report_tstp(*a): - print("SIGTSTP received", file=sys.stderr, flush=True) - raise SystemExit(19) - - signal.signal(signal.SIGWINCH, report_screen_size_change) - signal.signal(signal.SIGTSTP, report_tstp) - - if initial_print: - print(initial_print, flush=True, file=sys.stderr) - - output = { - 'test_env': os.environ.get('TEST_ENV_PASS', ''), - 'cwd': os.path.realpath(os.getcwd()), - 'font_family': get_options().font_family, - 'cols': read_screen_size(fd=sys.stderr.fileno()).cols, - 'stdin_data': sys.stdin.read(), - 'done': 'hello', - } - print(json.dumps(output, indent=2), file=sys.stderr, flush=True) - print('testing stdout', end='') - raise SystemExit(exit_code) - ''' + '\n\n' - cwd = os.path.realpath(tempfile.gettempdir()) - opts = self.set_options() - opts.config_overrides = 'font_family prewarm', - p = fork_prewarm_process(opts, use_exec=True) - if p is None: - return - env = os.environ.copy() - env.update({ - 'TEST_ENV_PASS': 'xyz', - 'KITTY_PREWARM_SOCKET': p.socket_env_var(), - 'KITTY_PREWARM_SOCKET_REAL_TTY': ' ' * 32, - 'TERM': 'xterm-kitty', - 'TERMINFO': terminfo_dir - }) - cols = 117 - - def wait_for_death(exit_code, timeout=5): - status = wait_for_child_death(pty.child_pid, timeout=timeout) - if status is None: - os.kill(pty.child_pid, signal.SIGKILL) - if status is None: - pty.process_input_from_child(0) - self.assertIsNotNone(status, f'prewarm wrapper process did not exit. Screen contents: {pty.screen_contents()}') - if isinstance(exit_code, signal.Signals): - self.assertTrue(os.WIFSIGNALED(status), 'prewarm wrapper did not die with a signal') - self.assertEqual(os.WTERMSIG(status), exit_code.value) - else: - with suppress(AttributeError): - self.assertEqual(os.waitstatus_to_exitcode(status), exit_code, pty.screen_contents()) - - if not self.is_ci: # signal delivery tests are pretty flakey on CI so give up on them - with self.subTest(msg='test SIGINT via signal to wrapper'): - pty = self.create_pty( - argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd) - pty.wait_till(lambda: 'child ready:' in pty.screen_contents()) - os.kill(pty.child_pid, signal.SIGINT) - pty.wait_till(lambda: 'KeyboardInterrupt' in pty.screen_contents()) - wait_for_death(signal.SIGINT) - - with self.subTest(msg='test SIGINT via Ctrl-c'): - pty = self.create_pty( - argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd) - pty.wait_till(lambda: 'child ready:' in pty.screen_contents()) - pty.write_to_child('\x03', flush=True) - pty.wait_till(lambda: 'KeyboardInterrupt' in pty.screen_contents()) - wait_for_death(signal.SIGINT) - - with self.subTest(msg='test SIGTSTP via Ctrl-z'): - pty = self.create_pty( - argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd) - pty.wait_till(lambda: 'child ready:' in pty.screen_contents()) - pty.write_to_child('\x1a', flush=True) - pty.wait_till(lambda: 'SIGTSTP received' in pty.screen_contents()) - wait_for_death(19) - - with self.subTest(msg='test SIGWINCH handling'): - pty = self.create_pty( - argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd) - pty.wait_till(lambda: 'child ready:' in pty.screen_contents()) - pty.set_window_size(columns=cols + 3) - pty.wait_till(lambda: f'Screen size changed: {cols + 3}' in pty.screen_contents()) - os.close(pty.master_fd) - - with self.subTest(msg='test env rewrite'): - pty = self.create_pty( - argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd) - pty.wait_till(lambda: 'child ready:' in pty.screen_contents()) - from kitty.child import environ_of_process - self.assertIn('/', environ_of_process(pty.child_pid).get('KITTY_PREWARM_SOCKET_REAL_TTY', '')) - os.close(pty.master_fd) - - with self.subTest(msg='test passing of data via cwd, env vars and stdin/stdout redirection'): - stdin_r, stdin_w = os.pipe() - os.set_inheritable(stdin_w, False) - stdout_r, stdout_w = os.pipe() - os.set_inheritable(stdout_r, False) - pty = self.create_pty( - argv=[kitty_exe(), '+runpy', src + f'socket_child_main({exit_code})'], cols=cols, env=env, cwd=cwd, - stdin_fd=stdin_r, stdout_fd=stdout_w) - stdin_data = 'testing--stdin-read' - with open(stdin_w, 'w') as f: - f.write(stdin_data) - - def has_json(): - s = pty.screen_contents().strip() - return 'hello' in s and s.endswith('}') - - pty.wait_till(has_json) - wait_for_death(exit_code) - output = json.loads(pty.screen_contents().strip()) - self.assertEqual(output['test_env'], env['TEST_ENV_PASS']) - self.assertEqual(output['cwd'], cwd) - self.assertEqual(output['font_family'], 'prewarm') - self.assertEqual(output['cols'], cols) - self.assertEqual(output['stdin_data'], stdin_data) - with open(stdout_r) as f: - stdout_data = f.read() - self.assertEqual(stdout_data, 'testing stdout') - def test_prewarming(self): from kitty.prewarm import fork_prewarm_process diff --git a/setup.py b/setup.py index 622cd40b4..693a4803b 100755 --- a/setup.py +++ b/setup.py @@ -920,7 +920,7 @@ def build_launcher(args: Options, launcher_dir: str = '.', bundle_type: str = 's os.makedirs(launcher_dir, exist_ok=True) os.makedirs(build_dir, exist_ok=True) objects = [] - for src in ('kitty/launcher/main.c', 'kitty/launcher/prewarm.c'): + for src in ('kitty/launcher/main.c',): obj = os.path.join(build_dir, src.replace('/', '-').replace('.c', '.o')) objects.append(obj) cmd = env.cc + cppflags + cflags + ['-c', src, '-o', obj] diff --git a/test.py b/test.py index 60870d5db..c117ffed8 100755 --- a/test.py +++ b/test.py @@ -3,7 +3,6 @@ import importlib import os -import sys import warnings from contextlib import contextmanager from tempfile import TemporaryDirectory @@ -25,9 +24,6 @@ def env_vars(**kw: str) -> Iterator[None]: def main() -> None: - if 'prewarmed' in getattr(sys, 'kitty_run_data'): - os.environ.pop('KITTY_PREWARM_SOCKET') - os.execlp(sys.executable, sys.executable, '+launch', __file__, *sys.argv[1:]) warnings.simplefilter('error') current_home = os.path.expanduser('~') + os.sep paths = os.environ.get('PATH', '/usr/local/sbin:/usr/local/bin:/usr/bin').split(os.pathsep)