Remove socket prewarming
The potential for breakage is too high, and I am working on an alternative solution that will be better long term. Prewarming is still used for kittens launched via keybindings
This commit is contained in:
parent
4cc0138a28
commit
44ccdd36d6
@ -23,10 +23,6 @@ following dependencies are installed first.
|
|||||||
should use a version of the source that matches the binary version as closely
|
should use a version of the source that matches the binary version as closely
|
||||||
as possible, since the two are tightly coupled.
|
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
|
Dependencies
|
||||||
----------------
|
----------------
|
||||||
|
|||||||
@ -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`)
|
- 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`)
|
- A new option :opt:`modify_font` to adjust various font metrics like underlines, cell sizes etc. (:pull:`5265`)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
A public key that programs can use to communicate securely with kitty using
|
||||||
the remote control protocol. The format is: :code:`protocol:key data`.
|
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
|
.. envvar:: WINDOWID
|
||||||
|
|
||||||
The id for the :term:`OS Window <os_window>` the program is running in. Only available
|
The id for the :term:`OS Window <os_window>` the program is running in. Only available
|
||||||
|
|||||||
@ -2,9 +2,7 @@
|
|||||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from kitty.conf.generate import write_output
|
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:
|
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
|
from kitty.options.definition import definition
|
||||||
write_output('kitty', definition)
|
write_output('kitty', definition)
|
||||||
nullable_colors = []
|
nullable_colors = []
|
||||||
|
|||||||
@ -148,7 +148,6 @@ def process_env() -> Dict[str, str]:
|
|||||||
ssl_env_var = getattr(sys, 'kitty_ssl_env_var', None)
|
ssl_env_var = getattr(sys, 'kitty_ssl_env_var', None)
|
||||||
if ssl_env_var is not None:
|
if ssl_env_var is not None:
|
||||||
ans.pop(ssl_env_var, None)
|
ans.pop(ssl_env_var, None)
|
||||||
ans.pop('KITTY_PREWARM_SOCKET', None)
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
@ -256,9 +255,6 @@ class Child:
|
|||||||
env['KITTY_LISTEN_ON'] = boss.listening_on
|
env['KITTY_LISTEN_ON'] = boss.listening_on
|
||||||
else:
|
else:
|
||||||
env.pop('KITTY_LISTEN_ON', None)
|
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:
|
if self.cwd:
|
||||||
# needed in case cwd is a symlink, in which case shells
|
# needed in case cwd is a symlink, in which case shells
|
||||||
# can use it to display the current directory name rather
|
# can use it to display the current directory name rather
|
||||||
@ -464,12 +460,7 @@ class Child:
|
|||||||
import termios
|
import termios
|
||||||
if self.child_fd is None:
|
if self.child_fd is None:
|
||||||
return False
|
return False
|
||||||
tty_name = self.foreground_environ.get('KITTY_PREWARM_SOCKET_REAL_TTY')
|
t = termios.tcgetattr(self.child_fd)
|
||||||
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)
|
|
||||||
if not t[3] & termios.ISIG:
|
if not t[3] & termios.ISIG:
|
||||||
return False
|
return False
|
||||||
cc = t[-1]
|
cc = t[-1]
|
||||||
|
|||||||
@ -268,11 +268,9 @@ read_exe_path(char *exe, size_t buf_sz) {
|
|||||||
}
|
}
|
||||||
#endif // }}}
|
#endif // }}}
|
||||||
|
|
||||||
extern void use_prewarmed_process(int argc, char *argv[], char *envp[]);
|
|
||||||
|
|
||||||
int main(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; }
|
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[PATH_MAX+1] = {0};
|
||||||
char exe_dir_buf[PATH_MAX+1] = {0};
|
char exe_dir_buf[PATH_MAX+1] = {0};
|
||||||
FREE_AFTER_FUNCTION const char *lc_ctype = NULL;
|
FREE_AFTER_FUNCTION const char *lc_ctype = NULL;
|
||||||
|
|||||||
@ -1,827 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Kovid Goyal <kovid at kovidgoyal.net>
|
|
||||||
*
|
|
||||||
* 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 <stdio.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <termios.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#ifdef __APPLE__
|
|
||||||
#include <util.h>
|
|
||||||
#include <mach-o/dyld.h>
|
|
||||||
#include <sys/syslimits.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#else
|
|
||||||
#include <pty.h>
|
|
||||||
#endif
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#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);
|
|
||||||
}
|
|
||||||
287
kitty/prewarm.py
287
kitty/prewarm.py
@ -1,15 +1,12 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import errno
|
|
||||||
import fcntl
|
import fcntl
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
import signal
|
import signal
|
||||||
import socket
|
|
||||||
import struct
|
|
||||||
import sys
|
import sys
|
||||||
import termios
|
import termios
|
||||||
import time
|
import time
|
||||||
@ -27,14 +24,14 @@ from typing import (
|
|||||||
from kitty.constants import kitty_exe, running_in_kitty
|
from kitty.constants import kitty_exe, running_in_kitty
|
||||||
from kitty.entry_points import main as main_entry_point
|
from kitty.entry_points import main as main_entry_point
|
||||||
from kitty.fast_data_types import (
|
from kitty.fast_data_types import (
|
||||||
CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, getpeereid,
|
CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, install_signal_handlers,
|
||||||
install_signal_handlers, read_signals, remove_signal_handlers, safe_pipe,
|
read_signals, remove_signal_handlers, safe_pipe, set_options,
|
||||||
set_options, set_use_os_log
|
set_use_os_log
|
||||||
)
|
)
|
||||||
from kitty.options.types import Options
|
from kitty.options.types import Options
|
||||||
from kitty.shm import SharedMemory
|
from kitty.shm import SharedMemory
|
||||||
from kitty.types import SignalInfo
|
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:
|
if TYPE_CHECKING:
|
||||||
from _typeshed import ReadableBuffer, WriteableBuffer
|
from _typeshed import ReadableBuffer, WriteableBuffer
|
||||||
@ -90,7 +87,6 @@ class PrewarmProcess:
|
|||||||
to_prewarm_stdin: int,
|
to_prewarm_stdin: int,
|
||||||
from_prewarm_stdout: int,
|
from_prewarm_stdout: int,
|
||||||
from_prewarm_death_notify: int,
|
from_prewarm_death_notify: int,
|
||||||
unix_socket_name: str,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.children: Dict[int, Child] = {}
|
self.children: Dict[int, Child] = {}
|
||||||
self.worker_pid = prewarm_process_pid
|
self.worker_pid = prewarm_process_pid
|
||||||
@ -99,10 +95,6 @@ class PrewarmProcess:
|
|||||||
self.read_from_process_fd = from_prewarm_stdout
|
self.read_from_process_fd = from_prewarm_stdout
|
||||||
self.poll = select.poll()
|
self.poll = select.poll()
|
||||||
self.poll.register(self.read_from_process_fd, select.POLLIN)
|
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:
|
def take_from_worker_fd(self, create_file: bool = False) -> int:
|
||||||
if create_file:
|
if create_file:
|
||||||
@ -375,20 +367,6 @@ def fork(shm_address: str, free_non_child_resources: Callable[[], None]) -> Tupl
|
|||||||
return 0, -1 # type: ignore
|
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])
|
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:
|
def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int) -> None:
|
||||||
# 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:
|
|
||||||
global parent_tty_name
|
global parent_tty_name
|
||||||
with suppress(OSError):
|
with suppress(OSError):
|
||||||
parent_tty_name = os.ttyname(sys.stdout.fileno())
|
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(stdin_fd, False)
|
||||||
os.set_blocking(stdout_fd, False)
|
os.set_blocking(stdout_fd, False)
|
||||||
signal_read_fd = install_signal_handlers(signal.SIGCHLD, signal.SIGUSR1)[0]
|
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 = select.poll()
|
||||||
poll.register(stdin_fd, select.POLLIN)
|
poll.register(stdin_fd, select.POLLIN)
|
||||||
poll.register(signal_read_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''
|
input_buf = output_buf = child_death_buf = b''
|
||||||
child_ready_fds: Dict[int, int] = {}
|
child_ready_fds: Dict[int, int] = {}
|
||||||
child_pid_map: 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():
|
for fd in get_all_non_child_fds():
|
||||||
if fd > -1:
|
if fd > -1:
|
||||||
safe_close(fd)
|
safe_close(fd)
|
||||||
unix_socket.close()
|
|
||||||
|
|
||||||
def check_event(event: int, err_msg: str) -> None:
|
def check_event(event: int, err_msg: str) -> None:
|
||||||
if event & select.POLLHUP:
|
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)
|
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
|
keep_type_checker_happy = True
|
||||||
try:
|
try:
|
||||||
while is_zygote and keep_type_checker_happy:
|
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)
|
handle_signals(event)
|
||||||
elif q == notify_child_death_fd:
|
elif q == notify_child_death_fd:
|
||||||
handle_notify_child_death(event)
|
handle_notify_child_death(event)
|
||||||
elif q == unix_socket.fileno():
|
|
||||||
handle_socket_client(event)
|
|
||||||
except (KeyboardInterrupt, EOFError, BrokenPipeError):
|
except (KeyboardInterrupt, EOFError, BrokenPipeError):
|
||||||
if is_zygote:
|
if is_zygote:
|
||||||
raise SystemExit(1)
|
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)
|
safe_close(fmd)
|
||||||
|
|
||||||
|
|
||||||
def get_socket_name(unix_socket: socket.socket) -> str:
|
def exec_main(stdin_read: int, stdout_write: int, death_notify_write: int) -> None:
|
||||||
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:
|
|
||||||
os.setsid()
|
os.setsid()
|
||||||
os.set_inheritable(stdin_read, False)
|
os.set_inheritable(stdin_read, False)
|
||||||
os.set_inheritable(stdout_write, False)
|
os.set_inheritable(stdout_write, False)
|
||||||
os.set_inheritable(death_notify_write, False)
|
os.set_inheritable(death_notify_write, False)
|
||||||
running_in_kitty(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
|
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
|
sys.stdout.reconfigure(line_buffering=True) # type: ignore
|
||||||
try:
|
try:
|
||||||
main(stdin_read, stdout_write, death_notify_write, unix_socket)
|
main(stdin_read, stdout_write, death_notify_write)
|
||||||
finally:
|
finally:
|
||||||
set_options(None)
|
set_options(None)
|
||||||
if is_zygote:
|
|
||||||
unix_socket.close()
|
|
||||||
|
|
||||||
|
|
||||||
def fork_prewarm_process(opts: Options, use_exec: bool = False) -> Optional[PrewarmProcess]:
|
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
|
child_pid = tp.pid
|
||||||
tp.returncode = 0 # prevent a warning when the popen object is deleted with the process still running
|
tp.returncode = 0 # prevent a warning when the popen object is deleted with the process still running
|
||||||
os.set_blocking(stdout_read, True)
|
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)
|
os.set_blocking(stdout_read, False)
|
||||||
else:
|
else:
|
||||||
unix_socket = random_unix_socket()
|
|
||||||
socket_name = get_socket_name(unix_socket)
|
|
||||||
child_pid = safer_fork()
|
child_pid = safer_fork()
|
||||||
if child_pid:
|
if child_pid:
|
||||||
# master
|
# master
|
||||||
if not use_exec:
|
|
||||||
unix_socket.close()
|
|
||||||
safe_close(stdin_read)
|
safe_close(stdin_read)
|
||||||
safe_close(stdout_write)
|
safe_close(stdout_write)
|
||||||
safe_close(death_notify_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:
|
if use_exec:
|
||||||
p.reload_kitty_config()
|
p.reload_kitty_config()
|
||||||
return p
|
return p
|
||||||
@ -874,5 +605,5 @@ def fork_prewarm_process(opts: Options, use_exec: bool = False) -> Optional[Prew
|
|||||||
safe_close(stdout_read)
|
safe_close(stdout_read)
|
||||||
safe_close(death_notify_read)
|
safe_close(death_notify_read)
|
||||||
set_options(opts)
|
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)
|
raise SystemExit(0)
|
||||||
|
|||||||
@ -9,9 +9,8 @@ import signal
|
|||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
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 (
|
from kitty.fast_data_types import (
|
||||||
CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, has_sigqueue, install_signal_handlers,
|
CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, has_sigqueue, install_signal_handlers,
|
||||||
read_signals, sigqueue
|
read_signals, sigqueue
|
||||||
@ -24,139 +23,6 @@ class Prewarm(BaseTest):
|
|||||||
|
|
||||||
maxDiff = None
|
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):
|
def test_prewarming(self):
|
||||||
from kitty.prewarm import fork_prewarm_process
|
from kitty.prewarm import fork_prewarm_process
|
||||||
|
|
||||||
|
|||||||
2
setup.py
2
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(launcher_dir, exist_ok=True)
|
||||||
os.makedirs(build_dir, exist_ok=True)
|
os.makedirs(build_dir, exist_ok=True)
|
||||||
objects = []
|
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'))
|
obj = os.path.join(build_dir, src.replace('/', '-').replace('.c', '.o'))
|
||||||
objects.append(obj)
|
objects.append(obj)
|
||||||
cmd = env.cc + cppflags + cflags + ['-c', src, '-o', obj]
|
cmd = env.cc + cppflags + cflags + ['-c', src, '-o', obj]
|
||||||
|
|||||||
4
test.py
4
test.py
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import warnings
|
import warnings
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
@ -25,9 +24,6 @@ def env_vars(**kw: str) -> Iterator[None]:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> 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')
|
warnings.simplefilter('error')
|
||||||
current_home = os.path.expanduser('~') + os.sep
|
current_home = os.path.expanduser('~') + os.sep
|
||||||
paths = os.environ.get('PATH', '/usr/local/sbin:/usr/local/bin:/usr/bin').split(os.pathsep)
|
paths = os.environ.get('PATH', '/usr/local/sbin:/usr/local/bin:/usr/bin').split(os.pathsep)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user