Skip to content

Commit 2d88d46

Browse files
committed
Preserve fd numbers in pass_fds
Also cleanup serialization of argv/env string arrays for exec
1 parent 6131f4c commit 2d88d46

File tree

5 files changed

+51
-76
lines changed

5 files changed

+51
-76
lines changed

kitty/child.c

Lines changed: 39 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,25 @@
1616
#include <sys/ioctl.h>
1717
#include <termios.h>
1818

19-
static char**
20-
serialize_string_tuple(PyObject *src) {
21-
Py_ssize_t sz = PyTuple_GET_SIZE(src);
19+
#define EXTRA_ENV_BUFFER_SIZE 64
2220

23-
char **ans = calloc(sz + 1, sizeof(char*));
24-
if (!ans) fatal("Out of memory");
21+
static char**
22+
serialize_string_tuple(PyObject *src, Py_ssize_t extra) {
23+
const Py_ssize_t sz = PyTuple_GET_SIZE(src);
24+
size_t required_size = sizeof(char*) * (1 + sz + extra);
25+
required_size += extra * EXTRA_ENV_BUFFER_SIZE;
26+
void *block = calloc(required_size, 1);
27+
if (!block) { PyErr_NoMemory(); return NULL; }
28+
char **ans = block;
2529
for (Py_ssize_t i = 0; i < sz; i++) {
26-
const char *pysrc = PyUnicode_AsUTF8(PyTuple_GET_ITEM(src, i));
27-
if (!pysrc) {
28-
PyErr_Clear();
29-
RAII_PyObject(u8, PyUnicode_AsEncodedString(PyTuple_GET_ITEM(src, i), "UTF-8", "ignore"));
30-
if (!u8) { PyErr_Print(); fatal("couldn't parse command line"); }
31-
ans[i] = calloc(PyBytes_GET_SIZE(u8) + 1, sizeof(char));
32-
if (ans[i] == NULL) fatal("Out of memory");
33-
memcpy(ans[i], PyBytes_AS_STRING(u8), PyBytes_GET_SIZE(u8));
34-
} else {
35-
size_t len = strlen(pysrc);
36-
ans[i] = calloc(len + 1, sizeof(char));
37-
if (ans[i] == NULL) fatal("Out of memory");
38-
memcpy(ans[i], pysrc, len);
39-
}
30+
PyObject *x = PyTuple_GET_ITEM(src, i);
31+
if (!PyUnicode_Check(x)) { free(block); PyErr_SetString(PyExc_TypeError, "string tuple must have only strings"); return NULL; }
32+
ans[i] = (char*)PyUnicode_AsUTF8(x);
33+
if (!ans[i]) { free(block); return NULL; }
4034
}
4135
return ans;
4236
}
4337

44-
static void
45-
free_string_tuple(char** data) {
46-
size_t i = 0;
47-
while(data[i]) free(data[i++]);
48-
free(data);
49-
}
50-
51-
extern char **environ;
52-
5338
static void
5439
write_to_stderr(const char *text) {
5540
size_t sz = strlen(text);
@@ -86,8 +71,10 @@ spawn(PyObject *self UNUSED, PyObject *args) {
8671
if (!PyArg_ParseTuple(args, "ssO!O!iiiiiiO!spO!", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd, &ready_read_fd, &ready_write_fd, &PyTuple_Type, &handled_signals_p, &kitten_exe, &forward_stdio, &PyTuple_Type, &pass_fds)) return NULL;
8772
char name[2048] = {0};
8873
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
89-
char **argv = serialize_string_tuple(argv_p);
90-
char **env = serialize_string_tuple(env_p);
74+
char **argv = serialize_string_tuple(argv_p, 0);
75+
if (!argv) return NULL;
76+
char **env = serialize_string_tuple(env_p, 1);
77+
if (!env) { free(argv); return NULL; }
9178
int handled_signals[16] = {0}, num_handled_signals = MIN((int)arraysz(handled_signals), PyTuple_GET_SIZE(handled_signals_p));
9279
for (Py_ssize_t i = 0; i < num_handled_signals; i++) handled_signals[i] = PyLong_AsLong(PyTuple_GET_ITEM(handled_signals_p, i));
9380

@@ -129,23 +116,29 @@ spawn(PyObject *self UNUSED, PyObject *args) {
129116
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
130117
safe_close(tfd, __FILE__, __LINE__);
131118

132-
int min_closed_fd = 3;
119+
fd_set passed_fds; FD_ZERO(&passed_fds); bool has_preserved_fds = false;
120+
if (forward_stdio) {
121+
int fd = safe_dup(STDOUT_FILENO);
122+
if (fd < 0) exit_on_err("dup() failed for forwarded STDOUT");
123+
FD_SET(fd, &passed_fds);
124+
size_t s = PyTuple_GET_SIZE(env_p);
125+
env[s] = (char*)(env + (s + 2));
126+
snprintf(env[s], EXTRA_ENV_BUFFER_SIZE, "KITTY_STDIO_FORWARDED=%d", fd);
127+
fd = safe_dup(STDERR_FILENO);
128+
if (fd < 0) exit_on_err("dup() failed for forwarded STDERR");
129+
FD_SET(fd, &passed_fds);
130+
has_preserved_fds = true;
131+
}
132+
133133
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(pass_fds); i++) {
134134
PyObject *pfd = PyTuple_GET_ITEM(pass_fds, i);
135135
if (!PyLong_Check(pfd)) exit_on_err("pass_fds must contain only integers");
136136
int fd = PyLong_AsLong(pfd);
137-
if (fd > -1) {
138-
if (fd == min_closed_fd) min_closed_fd++;
139-
else {
140-
if (safe_dup2(fd, min_closed_fd++) == -1) exit_on_err("dup2() failed for forwarded fd 1");
141-
safe_close(fd, __FILE__, __LINE__);
142-
}
137+
if (fd > -1 && fd < FD_SETSIZE) {
138+
FD_SET(fd, &passed_fds);
139+
has_preserved_fds = true;
143140
}
144141
}
145-
if (forward_stdio) {
146-
if (safe_dup2(STDOUT_FILENO, min_closed_fd++) == -1) exit_on_err("dup2() failed for forwarded fd 1");
147-
if (safe_dup2(STDERR_FILENO, min_closed_fd++) == -1) exit_on_err("dup2() failed for forwarded fd 2");
148-
}
149142
// Redirect stdin/stdout/stderr to the pty
150143
if (safe_dup2(slave, STDOUT_FILENO) == -1) exit_on_err("dup2() failed for fd number 1");
151144
if (safe_dup2(slave, STDERR_FILENO) == -1) exit_on_err("dup2() failed for fd number 2");
@@ -165,10 +158,10 @@ spawn(PyObject *self UNUSED, PyObject *args) {
165158
safe_close(ready_read_fd, __FILE__, __LINE__);
166159

167160
// Close any extra fds inherited from parent
168-
for (int c = min_closed_fd; c < 201; c++) safe_close(c, __FILE__, __LINE__);
161+
if (has_preserved_fds) { for (int c = 3; c < 256; c++) { if (!FD_ISSET(c, &passed_fds)) safe_close(c, __FILE__, __LINE__); } }
162+
else for (int c = 3; c < 256; c++) { safe_close(c, __FILE__, __LINE__); }
169163

170-
environ = env;
171-
execvp(exe, argv);
164+
execvpe(exe, argv, env);
172165
// Report the failure and exec kitten instead, so that we are not left
173166
// with a forked but not exec'ed process
174167
write_to_stderr("Failed to launch child: ");
@@ -196,32 +189,14 @@ spawn(PyObject *self UNUSED, PyObject *args) {
196189
break;
197190
}
198191
#undef exit_on_err
199-
free_string_tuple(argv);
200-
free_string_tuple(env);
192+
free(argv);
193+
free(env);
201194
if (PyErr_Occurred()) return NULL;
202195
return PyLong_FromLong(pid);
203196
}
204197

205-
#ifdef __APPLE__
206-
#include <crt_externs.h>
207-
#else
208-
extern char **environ;
209-
#endif
210-
211-
static PyObject*
212-
clearenv_py(PyObject *self UNUSED, PyObject *args UNUSED) {
213-
#ifdef __APPLE__
214-
char **e = *_NSGetEnviron();
215-
if (e) *e = NULL;
216-
#else
217-
if (environ) *environ = NULL;
218-
#endif
219-
Py_RETURN_NONE;
220-
}
221-
222198
static PyMethodDef module_methods[] = {
223199
METHODB(spawn, METH_VARARGS),
224-
{"clearenv", clearenv_py, METH_NOARGS, ""},
225200
{NULL, NULL, 0, NULL} /* Sentinel */
226201
};
227202

kitty/child.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ def get_final_env(self) -> dict[str, str]:
256256
env['KITTY_LISTEN_ON'] = boss.listening_on
257257
else:
258258
env.pop('KITTY_LISTEN_ON', None)
259+
env.pop('KITTY_STDIO_FORWARDED', None)
259260
if self.cwd:
260261
# needed in case cwd is a symlink, in which case shells
261262
# can use it to display the current directory name rather
@@ -268,8 +269,6 @@ def get_final_env(self) -> dict[str, str]:
268269
elif opts.terminfo_type == 'direct':
269270
env['TERMINFO'] = base64_terminfo_data()
270271
env['KITTY_INSTALLATION_DIR'] = kitty_base_dir
271-
if opts.forward_stdio:
272-
env['KITTY_STDIO_FORWARDED'] = '3'
273272
self.unmodified_argv = list(self.argv)
274273
if not self.should_run_via_run_shell_kitten and 'disabled' not in opts.shell_integration:
275274
from .shell_integration import modify_shell_environ
@@ -344,11 +343,6 @@ def fork(self) -> Optional[int]:
344343
final_exe, cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd,
345344
ready_read_fd, ready_write_fd, tuple(handled_signals), kitten_exe(), opts.forward_stdio, pass_fds)
346345
os.close(slave)
347-
for x in self.pass_fds:
348-
if isinstance(x, int):
349-
os.close(x)
350-
else:
351-
x.close()
352346
self.pass_fds = ()
353347
self.pid = pid
354348
self.child_fd = master

kitty/fast_data_types.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1671,7 +1671,6 @@ class SingleKey:
16711671

16721672
def set_use_os_log(yes: bool) -> None: ...
16731673
def get_docs_ref_map() -> bytes: ...
1674-
def clearenv() -> None: ...
16751674
def set_clipboard_data_types(ct: int, mime_types: Tuple[str, ...]) -> None: ...
16761675
def get_clipboard_mime(ct: int, mime: Optional[str], callback: Callable[[bytes], None]) -> None: ...
16771676
def run_with_activation_token(func: Callable[[str], None]) -> bool: ...

kitty/options/definition.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3311,11 +3311,11 @@
33113311

33123312

33133313
opt('forward_stdio', 'no', option_type='to_bool', long_text='''
3314-
Forward STDOUT and STDERR of the kitty process to child processes
3315-
as file descriptors 3 and 4. This is useful for debugging as it
3314+
Forward STDOUT and STDERR of the kitty process to child processes.
3315+
This is useful for debugging as it
33163316
allows child processes to print to kitty's STDOUT directly. For example,
3317-
:code:`echo hello world >&3` in a shell will print to the parent kitty's
3318-
STDOUT. When enabled, this also sets the :code:`KITTY_STDIO_FORWARDED=3`
3317+
:code:`echo hello world >&$KITTY_STDIO_FORWARDED` in a shell will print
3318+
to the parent kitty's STDOUT. Sets the :code:`KITTY_STDIO_FORWARDED=fdnum`
33193319
environment variable so child processes know about the forwarding.
33203320
''')
33213321

kitty/safe-wrappers.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ safe_close(int fd, const char* file UNUSED, const int line UNUSED) {
9898
while(close(fd) != 0 && errno == EINTR);
9999
}
100100

101+
static inline int
102+
safe_dup(int a) {
103+
int ret;
104+
while((ret = dup(a)) < 0 && errno == EINTR);
105+
return ret;
106+
}
107+
101108
static inline int
102109
safe_dup2(int a, int b) {
103110
int ret;

0 commit comments

Comments
 (0)