/* * Copyright © 2001,2002 Red Hat, Inc. * Copyright © 2009, 2010, 2019, 2020 Christian Persch * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ #include "config.h" #include "spawn.hh" #include "libc-glue.hh" #include #include "vteptyinternal.hh" #include "vtespawn.hh" #include "debug.h" #include "reaper.hh" #include #include #include #include #include #include #include #include "glib-glue.hh" #ifdef WITH_SYSTEMD #include "systemd.hh" #endif #include "vtedefines.hh" #include "missing.hh" namespace vte::base { static int set_cloexec_cb(void* data, int fd) { if (fd >= *reinterpret_cast(data)) { auto r = vte::libc::fd_set_cloexec(fd); /* Ignore EBADF because the libc or fallback implementation * of fdwalk may call this function on invalid file descriptors. */ if (r < 0 && errno == EBADF) r = 0; return r; } return 0; } static int cloexec_from(int fd) { return fdwalk(set_cloexec_cb, &fd); } static bool make_pipe(int flags, vte::libc::FD& read_fd, vte::libc::FD& write_fd, vte::glib::Error& error) { int fds[2] = { -1, -1 }; if (!g_unix_open_pipe(fds, flags, error)) return false; read_fd = fds[0]; write_fd = fds[1]; return true; } /* Code for read_ints copied from glib/glib/gspawn.c, there under LGPL2.1+, * and used here under LGPL3+. * * Copyright 2000 Red Hat, Inc. */ static bool read_ints(int fd, int* buf, int n_ints_in_buf, int *n_ints_read, int timeout, GPollFD *cancellable_pollfd, vte::glib::Error& error) { GPollFD pollfds[2]; auto n_pollfds = unsigned{0}; if (timeout >= 0 || cancellable_pollfd != nullptr) { if (vte::libc::fd_set_nonblock(fd) < 0) { auto errsv = vte::libc::ErrnoSaver{}; error.set(G_IO_ERROR, g_io_error_from_errno(errsv), _("Failed to set pipe nonblocking: %s"), g_strerror(errsv)); return false; } pollfds[0].fd = fd; pollfds[0].events = G_IO_IN | G_IO_HUP | G_IO_ERR; n_pollfds = 1; if (cancellable_pollfd != nullptr) { pollfds[1] = *cancellable_pollfd; n_pollfds = 2; } } else n_pollfds = 0; auto start_time = int64_t{0}; if (timeout >= 0) start_time = g_get_monotonic_time(); auto bytes = size_t{0}; while (true) { if (bytes >= sizeof(int)*2) break; /* give up, who knows what happened, should not be * possible. */ again: if (n_pollfds != 0) { pollfds[0].revents = pollfds[1].revents = 0; auto const r = g_poll(pollfds, n_pollfds, timeout); /* Update timeout */ if (timeout >= 0) { timeout -= (g_get_monotonic_time () - start_time) / 1000; if (timeout < 0) timeout = 0; } if (r < 0 && errno == EINTR) goto again; if (r < 0) { auto errsv = vte::libc::ErrnoSaver{}; error.set(G_IO_ERROR, g_io_error_from_errno(errsv), _("poll error: %s"), g_strerror(errsv)); return false; } if (r == 0) { auto errsv = vte::libc::ErrnoSaver{}; error.set_literal(G_IO_ERROR, G_IO_ERROR_TIMED_OUT, _("Operation timed out")); return false; } /* If the passed-in poll FD becomes readable, that's the signal * to cancel the operation. We do NOT actually read from its FD! */ if (n_pollfds == 2 && pollfds[1].revents) { auto errsv = vte::libc::ErrnoSaver{}; error.set_literal(G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was cancelled")); return false; } /* Now we know we can try to read from the child */ } auto const chunk = read(fd, ((char*)buf) + bytes, sizeof(int) * n_ints_in_buf - bytes); if (chunk < 0 && errno == EINTR) goto again; if (chunk < 0) { auto errsv = vte::libc::ErrnoSaver{}; /* Some weird shit happened, bail out */ error.set(G_IO_ERROR, g_io_error_from_errno(errsv), _("Failed to read from child pipe (%s)"), g_strerror(errsv)); return false; } else if (chunk == 0) break; /* EOF */ else /* chunk > 0 */ bytes += chunk; } *n_ints_read = int(bytes / sizeof(int)); return true; } static char** merge_environ(char** envp /* consumed */, char const* cwd, bool inherit) { auto table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); if (inherit) { auto parent_environ = vte::glib::take_strv(g_get_environ()); if (parent_environ) { auto penvv = parent_environ.get(); for (auto i = unsigned{0}; penvv[i] != NULL; ++i) { auto name = g_strdup(penvv[i]); auto value = strchr(name, '='); if (value) { *value = '\0'; value = g_strdup(value + 1); } g_hash_table_replace(table, name, value); /* takes ownership of name and value */ } } } /* Make sure the one in envp overrides the default. */ g_hash_table_replace(table, g_strdup("TERM"), g_strdup(VTE_TERMINFO_NAME)); if (envp) { for (auto i = unsigned{0}; envp[i] != nullptr; ++i) { auto name = g_strdup(envp[i]); auto value = strchr(name, '='); if (value) { *value = '\0'; value = g_strdup(value + 1); } g_hash_table_replace(table, name, value); /* takes ownership of name and value */ } g_strfreev(envp); } /* Always set this ourself, not allowing replacing from envp */ g_hash_table_replace(table, g_strdup("VTE_VERSION"), g_strdup_printf("%u", VTE_VERSION_NUMERIC)); g_hash_table_replace(table, g_strdup("COLORTERM"), g_strdup("truecolor")); /* We need to put the working directory also in PWD, so that * e.g. bash starts in the right directory if @directory is a symlink. * See bug #502146 and #758452. * * If chdir to cwd fails, and we fall back to the fallback cwd, PWD will * be set to a directory != the actual working directory, but that's not * a problem since PWD is only used when it's equal to the actual working * directory. */ if (cwd) g_hash_table_replace(table, g_strdup("PWD"), g_strdup(cwd)); auto array = g_ptr_array_sized_new(g_hash_table_size(table) + 1); auto iter = GHashTableIter{}; g_hash_table_iter_init(&iter, table); char *name, *value; while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) { if (value) g_ptr_array_add(array, g_strconcat(name, "=", value, nullptr)); } g_hash_table_destroy(table); g_ptr_array_add(array, nullptr); return reinterpret_cast(g_ptr_array_free(array, false)); } void SpawnContext::prepare_environ() { m_envv = vte::glib::take_strv(merge_environ(m_envv.release(), m_cwd.get(), inherit_environ())); } char const* SpawnContext::search_path() const noexcept { auto const path = m_search_path ? g_environ_getenv(environ(), "PATH") : nullptr; return path ? : "/bin:/usr/bin"; } size_t SpawnContext::workbuf_size() const noexcept { auto const path = search_path(); return std::max(path ? strlen(path) + strlen(arg0()) + 2 /* leading '/' plus NUL terminator */ : 0, (g_strv_length(argv()) + 2) * sizeof(char*)); } /* This function is called between fork and execve/_exit and so must be * async-signal-safe; see man:signal-safety(7). */ SpawnContext::ExecError SpawnContext::exec(vte::libc::FD& child_report_error_pipe_write, void* workbuf, size_t workbufsize) noexcept { /* NOTE! This function must not rely on smart pointers to * release their object, since the destructors are NOT run * when the exec succeeds! */ _VTE_DEBUG_IF(VTE_DEBUG_MISC) { g_printerr ("Spawning command:\n"); auto argv = m_argv.get(); for (auto i = 0; argv[i] != NULL; i++) { g_printerr(" argv[%d] = %s\n", i, argv[i]); } if (m_envv) { auto envv = m_envv.get(); for (auto i = 0; envv[i] != NULL; i++) { g_printerr(" env[%d] = %s\n", i, envv[i]); } } g_printerr(" directory: %s\n", m_cwd ? m_cwd.get() : "(none)"); } /* Unblock all signals */ sigset_t set; sigemptyset(&set); if (pthread_sigmask(SIG_SETMASK, &set, nullptr) == -1) { auto errsv = vte::libc::ErrnoSaver{}; _vte_debug_print(VTE_DEBUG_PTY, "%s failed: %s\n", "pthread_sigmask", g_strerror(errsv)); return ExecError::SIGMASK; } /* Reset the handlers for all signals to their defaults. The parent * (or one of the libraries it links to) may have changed one to be ignored. * Esp. SIGPIPE since it ensures this process terminates when we write * to child_err_report_pipe after the parent has exited. */ for (auto n = int{1}; n < NSIG; ++n) { if (n == SIGSTOP || n == SIGKILL) continue; signal(n, SIG_DFL); } /* Close all file descriptors on exec. Note that this includes * child_error_report_pipe_write, which keeps the parent from blocking * forever on the other end of that pipe. */ if (cloexec_from(3) < 0) return ExecError::FDWALK; /* Working directory */ if (m_cwd && chdir(m_cwd.get()) < 0) { /* If the fallback fails too, make sure to return the errno * from the original cwd, not the fallback cwd. */ auto errsv = vte::libc::ErrnoSaver{}; if (m_fallback_cwd && chdir(m_fallback_cwd.get()) < 0) return ExecError::CHDIR; errsv.reset(); } /* Session */ if (!(pty()->flags() & VTE_PTY_NO_SESSION)) { /* This starts a new session; we become its process-group leader, * and lose our controlling TTY. */ _vte_debug_print(VTE_DEBUG_PTY, "Starting new session\n"); if (setsid() == -1) { auto errsv = vte::libc::ErrnoSaver{}; _vte_debug_print(VTE_DEBUG_PTY, "%s failed: %s\n", "setsid", g_strerror(errsv)); return ExecError::SETSID; } } auto peer_fd = pty()->get_peer(true /* cloexec */); if (peer_fd == -1) return ExecError::GETPTPEER; #ifdef TIOCSCTTY /* On linux, opening the PTY peer above already made it our controlling TTY (since * previously there was none, after the setsid() call). However, it appears that e.g. * on *BSD, that doesn't happen, so we need this explicit ioctl here. */ if (!(pty()->flags() & VTE_PTY_NO_CTTY)) { if (ioctl(peer_fd, TIOCSCTTY, peer_fd) != 0) { auto errsv = vte::libc::ErrnoSaver{}; _vte_debug_print(VTE_DEBUG_PTY, "%s failed: %s\n", "ioctl(TIOCSCTTY)", g_strerror(errsv)); return ExecError::SCTTY; } } #endif /* Replace the placeholders with the FD assignment for the PTY */ m_fd_map[0].first = peer_fd; m_fd_map[1].first = peer_fd; m_fd_map[2].first = peer_fd; /* Assign FDs */ auto const n_fd_map = m_fd_map.size(); for (auto i = size_t{0}; i < n_fd_map; ++i) { auto [source_fd, target_fd] = m_fd_map[i]; /* -1 means the source_fd is only in the map so that it can * be checked for conflicts with other target FDs. It may be * re-assigned while relocating other FDs. */ if (target_fd == -1) continue; /* We want to move source_fd to target_fd */ if (target_fd != source_fd) { /* Need to check if target_fd is an FDs in the FD list. * If so, need to re-assign the source FD(s) first. */ for (auto j = size_t{0}; j < n_fd_map; ++j) { auto const [from_fd, to_fd] = m_fd_map[j]; if (from_fd != target_fd) continue; auto new_from_fd = vte::libc::fd_dup_cloexec(from_fd, target_fd + 1); if (new_from_fd == -1) return ExecError::DUP; for (auto k = j; k < n_fd_map; ++k) { if (m_fd_map[k].first == from_fd) m_fd_map[k].first = new_from_fd; } /* Now that we have updated all references to the old * source FD in the map, we can close the FD. (Not * strictly necessary since it'll be dup2'd over * anyway.) */ if (from_fd == child_report_error_pipe_write.get()) { /* Need to report the new pipe write FD back to the caller. */ child_report_error_pipe_write = new_from_fd; } else { (void)close(from_fd); } break; } } /* source_fd may have been changed by the loop above */ source_fd = m_fd_map[i].first; if (target_fd == source_fd) { /* Already assigned correctly, but need to remove FD_CLOEXEC */ if (vte::libc::fd_unset_cloexec(target_fd) == -1) return ExecError::UNSET_CLOEXEC; } else { /* Now we know that target_fd can be safely overwritten. */ if (vte::libc::fd_dup2(source_fd, target_fd) == -1) return ExecError::DUP2; } } /* Finally call an extra child setup */ if (m_child_setup) m_child_setup(m_child_setup_data.get()); /* exec */ _vte_execute(arg0(), argv(), environ(), search_path(), workbuf, workbufsize); /* If we get here, exec failed */ return ExecError::EXEC; } SpawnOperation::~SpawnOperation() { if (m_cancellable && m_cancellable_pollfd.fd != -1) g_cancellable_release_fd(m_cancellable.get()); if (m_pid != -1) { /* Since we're not passing the PID back to the caller, * we need to kill and reap it ourself. */ if (m_kill_pid) { auto const pgrp = getpgid(m_pid); /* Make sure not to kill ourself, if the child died before * it could call setsid()! */ if (pgrp != -1 && pgrp != getpgid(getpid())) kill(-pgrp, SIGHUP); kill(m_pid, SIGHUP); } vte_reaper_add_child(m_pid); } } bool SpawnOperation::prepare(vte::glib::Error& error) { #ifndef WITH_SYSTEMD if (context().require_systemd_scope()) { error.set_literal(G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "systemd not available"); return false; } #endif if (m_cancellable && !g_cancellable_make_pollfd(m_cancellable.get(), &m_cancellable_pollfd)) { auto errsv = vte::libc::ErrnoSaver{}; error.set(G_IO_ERROR, g_io_error_from_errno(errsv), "Failed to make cancellable pollfd: %s", g_strerror(errsv)); return false; } auto child_report_error_pipe_read = vte::libc::FD{}; auto child_report_error_pipe_write = vte::libc::FD{}; if (!make_pipe(FD_CLOEXEC, child_report_error_pipe_read, child_report_error_pipe_write, error)) return false; /* allocate workbuf for SpawnContext::Exec() */ auto const workbufsize = context().workbuf_size(); auto workbuf = vte::glib::take_free_ptr(g_try_malloc(workbufsize)); if (!workbuf) { auto errsv = vte::libc::ErrnoSaver{}; error.set(G_IO_ERROR, g_io_error_from_errno(errsv), "Failed to allocate workbuf: %s", g_strerror(errsv)); return false; } /* Need to add the write end of the pipe to the FD map, so * that the FD re-arranging code knows it needs to preserve * the FD and not dup2 over it. * Target -1 means that no actual re-assignment will take place. */ context().add_map_fd(child_report_error_pipe_write.get(), -1); auto const pid = fork(); if (pid < 0) { auto errsv = vte::libc::ErrnoSaver{}; error.set(G_IO_ERROR, g_io_error_from_errno(errsv), "Failed to fork: %s", g_strerror(errsv)); return false; } if (pid == 0) { /* Child */ child_report_error_pipe_read.reset(); auto const err = context().exec(child_report_error_pipe_write, workbuf.get(), workbufsize); /* Manually free the workbuf */ g_free(workbuf.release()); /* If we get here, exec failed. Write the error to the pipe and exit. */ _vte_write_err(child_report_error_pipe_write.get(), int(err)); _exit(127); return true; } /* Parent */ m_pid = pid; m_child_report_error_pipe_read = std::move(child_report_error_pipe_read); return true; } bool SpawnOperation::run(vte::glib::Error& error) noexcept { int buf[2] = {G_SPAWN_ERROR_FAILED, ENOSYS}; auto n_read = int{0}; if (!read_ints(m_child_report_error_pipe_read.get(), buf, 2, &n_read, m_timeout, &m_cancellable_pollfd, error)) return false; if (n_read >= 2) { /* Spawn failed. buf[0] contains an error from * SpawnContext::ExecError, and buf[1] contains errno. */ /* The process will have called _exit(127) already, no need to kill it */ m_kill_pid = false; auto const err = buf[1]; switch (SpawnContext::ExecError(buf[0])) { case SpawnContext::ExecError::CHDIR: { auto cwd = vte::glib::take_string(context().cwd() ? g_utf8_make_valid(context().cwd(), -1) : nullptr); error.set(G_IO_ERROR, g_io_error_from_errno(err), _("Failed to change to directory “%s”: %s"), cwd.get(), g_strerror(err)); break; } case SpawnContext::ExecError::DUP: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Failed to duplicate file descriptor: %s", g_strerror(err)); break; case SpawnContext::ExecError::DUP2: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Failed to duplicate file descriptor (dup2): %s", g_strerror(err)); break; case SpawnContext::ExecError::EXEC: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Failed to execve: %s", g_strerror(err)); break; case SpawnContext::ExecError::FDWALK: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Failed to fdwalk: %s", g_strerror(err)); break; case SpawnContext::ExecError::GETPTPEER: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Failed to open PTY peer: %s", g_strerror(err)); break; case SpawnContext::ExecError::SCTTY: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Failed to set controlling TTY: %s", g_strerror(err)); break; case SpawnContext::ExecError::SETSID: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Failed to start session: %s", g_strerror(err)); break; case SpawnContext::ExecError::SIGMASK: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Failed to set signal mask: %s", g_strerror(err)); break; case SpawnContext::ExecError::UNSET_CLOEXEC: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Failed to make file descriptor not cloexec: %s", g_strerror(err)); break; default: error.set(G_IO_ERROR, g_io_error_from_errno(err), "Unknown error: %s", g_strerror(err)); break; } auto arg0 = vte::glib::take_string(g_utf8_make_valid(context().argv()[0], -1)); g_prefix_error(error, _("Failed to execute child process “%s”: "), arg0.get()); return false; } /* Spawn succeeded */ #ifdef WITH_SYSTEMD if (context().systemd_scope() && !vte::systemd::create_scope_for_pid_sync(m_pid, m_timeout, // FIXME: recalc timeout m_cancellable.get(), error)) { if (context().require_systemd_scope()) return false; _vte_debug_print(VTE_DEBUG_PTY, "Failed to create systemd scope: %s", error.message()); error.reset(); } #endif // WITH_SYSTEMD return true; } void SpawnOperation::run_in_thread(GTask* task) noexcept { auto error = vte::glib::Error{}; if (run(error)) g_task_return_int(task, ssize_t{release_pid()}); else g_task_return_error(task, error.release()); } void SpawnOperation::run_async(std::unique_ptr op, void* source_tag, GAsyncReadyCallback callback, void* user_data) { /* Spawning is split into the fork() phase, and waiting for the child to * exec or report an error. This is done so that the fork is happening on * the main thread; see issue vte#118. */ auto error = vte::glib::Error{}; auto rv = op->prepare(error); /* Create a GTask to run the user-provided callback, and transfers * ownership of @op to the task. */ auto task = vte::glib::take_ref(g_task_new(op->context().pty_wrapper(), op->m_cancellable.get(), callback, user_data)); g_task_set_source_tag(task.get(), source_tag); g_task_set_task_data(task.get(), op.release(), delete_cb); // g_task_set_name(task.get(), "vte-spawn-async"); if (!rv) return g_task_return_error(task.get(), error.release()); /* Async read from the child */ g_task_run_in_thread(task.get(), run_in_thread_cb); } bool SpawnOperation::run_sync(SpawnOperation& op, GPid* pid, vte::glib::Error& error) { auto rv = op.prepare(error) && op.run(error); if (rv) *pid = op.release_pid(); else *pid = -1; return rv; } } // namespace vte::base