1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
/*
* child.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include "safe-wrappers.h"
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <termios.h>
#define EXTRA_ENV_BUFFER_SIZE 64
static char**
serialize_string_tuple(PyObject *src, Py_ssize_t extra) {
const Py_ssize_t sz = PyTuple_GET_SIZE(src);
size_t required_size = sizeof(char*) * (1 + sz + extra);
required_size += extra * EXTRA_ENV_BUFFER_SIZE;
void *block = calloc(required_size, 1);
if (!block) { PyErr_NoMemory(); return NULL; }
char **ans = block;
for (Py_ssize_t i = 0; i < sz; i++) {
PyObject *x = PyTuple_GET_ITEM(src, i);
if (!PyUnicode_Check(x)) { free(block); PyErr_SetString(PyExc_TypeError, "string tuple must have only strings"); return NULL; }
ans[i] = (char*)PyUnicode_AsUTF8(x);
if (!ans[i]) { free(block); return NULL; }
}
return ans;
}
static void
write_to_stderr(const char *text) {
size_t sz = strlen(text);
size_t written = 0;
while(written < sz) {
ssize_t amt = write(2, text + written, sz - written);
if (amt == 0) break;
if (amt < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
break;
}
written += amt;
}
}
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
static void
wait_for_terminal_ready(int fd) {
char data;
while(1) {
int ret = read(fd, &data, 1);
if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue;
break;
}
}
static PyObject*
spawn(PyObject *self UNUSED, PyObject *args) {
PyObject *argv_p, *env_p, *handled_signals_p, *pass_fds;
int master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd, forward_stdio;
const char *kitten_exe;
char *cwd, *exe;
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;
char name[2048] = {0};
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
char **argv = serialize_string_tuple(argv_p, 0);
if (!argv) return NULL;
char **env = serialize_string_tuple(env_p, 1);
if (!env) { free(argv); return NULL; }
int handled_signals[16] = {0}, num_handled_signals = MIN((int)arraysz(handled_signals), PyTuple_GET_SIZE(handled_signals_p));
for (Py_ssize_t i = 0; i < num_handled_signals; i++) handled_signals[i] = PyLong_AsLong(PyTuple_GET_ITEM(handled_signals_p, i));
#if PY_VERSION_HEX >= 0x03070000
PyOS_BeforeFork();
#endif
pid_t pid = fork();
switch(pid) {
case 0: {
// child
#if PY_VERSION_HEX >= 0x03070000
PyOS_AfterFork_Child();
#endif
const struct sigaction act = {.sa_handler=SIG_DFL};
#define SA(which) if (sigaction(which, &act, NULL) != 0) exit_on_err("sigaction() in child process failed");
for (int si = 0; si < num_handled_signals; si++) { SA(handled_signals[si]); }
// See _Py_RestoreSignals in signalmodule.c for a list of signals python nukes
#ifdef SIGPIPE
SA(SIGPIPE)
#endif
#ifdef SIGXFSZ
SA(SIGXFSZ);
#endif
#ifdef SIGXFZ
SA(SIGXFZ);
#endif
#undef SA
sigset_t signals; sigemptyset(&signals);
if (sigprocmask(SIG_SETMASK, &signals, NULL) != 0) exit_on_err("sigprocmask() in child process failed");
// Use only signal-safe functions (man 7 signal-safety)
if (chdir(cwd) != 0) {
if (access(".", X_OK) != 0) { // existing cwd does not exist or dont have permissions for it
if (chdir("/") != 0) {} // ignore failure to chdir to /
}
};
if (setsid() == -1) exit_on_err("setsid() in child process failed");
// Establish the controlling terminal (see man 7 credentials)
int tfd = safe_open(name, O_RDWR | O_CLOEXEC, 0);
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
// On BSD open() does not establish the controlling terminal
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
safe_close(tfd, __FILE__, __LINE__);
fd_set passed_fds; FD_ZERO(&passed_fds); bool has_preserved_fds = false;
if (forward_stdio) {
int fd = safe_dup(STDOUT_FILENO);
if (fd < 0) exit_on_err("dup() failed for forwarded STDOUT");
FD_SET(fd, &passed_fds);
size_t s = PyTuple_GET_SIZE(env_p);
env[s] = (char*)(env + (s + 2));
snprintf(env[s], EXTRA_ENV_BUFFER_SIZE, "KITTY_STDIO_FORWARDED=%d", fd);
fd = safe_dup(STDERR_FILENO);
if (fd < 0) exit_on_err("dup() failed for forwarded STDERR");
FD_SET(fd, &passed_fds);
has_preserved_fds = true;
}
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(pass_fds); i++) {
PyObject *pfd = PyTuple_GET_ITEM(pass_fds, i);
if (!PyLong_Check(pfd)) exit_on_err("pass_fds must contain only integers");
int fd = PyLong_AsLong(pfd);
if (fd > -1 && fd < FD_SETSIZE) {
FD_SET(fd, &passed_fds);
has_preserved_fds = true;
}
}
// Redirect stdin/stdout/stderr to the pty
if (safe_dup2(slave, STDOUT_FILENO) == -1) exit_on_err("dup2() failed for fd number 1");
if (safe_dup2(slave, STDERR_FILENO) == -1) exit_on_err("dup2() failed for fd number 2");
if (stdin_read_fd > -1) {
if (safe_dup2(stdin_read_fd, STDIN_FILENO) == -1) exit_on_err("dup2() failed for fd number 0");
safe_close(stdin_read_fd, __FILE__, __LINE__);
safe_close(stdin_write_fd, __FILE__, __LINE__);
} else {
if (safe_dup2(slave, STDIN_FILENO) == -1) exit_on_err("dup2() failed for fd number 0");
}
safe_close(slave, __FILE__, __LINE__);
safe_close(master, __FILE__, __LINE__);
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
safe_close(ready_write_fd, __FILE__, __LINE__);
wait_for_terminal_ready(ready_read_fd);
safe_close(ready_read_fd, __FILE__, __LINE__);
// Close any extra fds inherited from parent
if (has_preserved_fds) { for (int c = 3; c < 256; c++) { if (!FD_ISSET(c, &passed_fds)) safe_close(c, __FILE__, __LINE__); } }
else for (int c = 3; c < 256; c++) { safe_close(c, __FILE__, __LINE__); }
extern char **environ;
environ = env;
execvp(exe, argv);
// Report the failure and exec kitten instead, so that we are not left
// with a forked but not exec'ed process
write_to_stderr("Failed to launch child: ");
write_to_stderr(exe);
write_to_stderr("\nWith error: ");
write_to_stderr(strerror(errno));
write_to_stderr("\n");
execlp(kitten_exe, "kitten", "__hold_till_enter__", NULL);
exit(EXIT_FAILURE);
break;
}
case -1: {
#if PY_VERSION_HEX >= 0x03070000
int saved_errno = errno;
PyOS_AfterFork_Parent();
errno = saved_errno;
#endif
PyErr_SetFromErrno(PyExc_OSError);
break;
}
default:
#if PY_VERSION_HEX >= 0x03070000
PyOS_AfterFork_Parent();
#endif
break;
}
#undef exit_on_err
free(argv);
free(env);
if (PyErr_Occurred()) return NULL;
return PyLong_FromLong(pid);
}
static PyMethodDef module_methods[] = {
METHODB(spawn, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_child(PyObject *module) {
PyModule_AddIntMacro(module, CLD_KILLED);
PyModule_AddIntMacro(module, CLD_STOPPED);
PyModule_AddIntMacro(module, CLD_EXITED);
PyModule_AddIntMacro(module, CLD_CONTINUED);
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}
|