mirror of
https://github.com/swaywm/sway.git
synced 2026-03-20 05:34:28 -04:00
cmd_exec_process is used whenever sway is meant to execute a child process on behalf of the user, and had a lot of complexity. In order to avoid having to wait on the user's process, a double-fork was used, which in turn required us to wait on the outer process. In order to track the child PID for launcher purposes, a pipe was used to transmit the PID back to sway. This resulted in sway blocking for 5-6 ms per exec on my system, which is quite significant. The error handling was also quite lacking - the read loop did not handle errors at all for example. Instead, teach sway to handle SIGCHLD and do away with the double-fork. This in turn allows us to get rid of the pipe as we can record the child's PID directly. This reduces the time we block to just 1.5 ms on my system. We'd be able to get down to just 150 µs if we could use posix_spawn(3), but posix_spawn(3) cannot reset NOFILE. clone(2) or vfork(2) would be alternatives, but that presents portability issues. This change is replicated for swaybar, swaybg and swaynag handling, which had similar albeit less complicated implementations.
151 lines
3.5 KiB
C
151 lines
3.5 KiB
C
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include "log.h"
|
|
#include "sway/server.h"
|
|
#include "sway/swaynag.h"
|
|
#include "util.h"
|
|
|
|
static void handle_swaynag_client_destroy(struct wl_listener *listener,
|
|
void *data) {
|
|
struct swaynag_instance *swaynag =
|
|
wl_container_of(listener, swaynag, client_destroy);
|
|
wl_list_remove(&swaynag->client_destroy.link);
|
|
wl_list_init(&swaynag->client_destroy.link);
|
|
swaynag->client = NULL;
|
|
}
|
|
|
|
bool swaynag_spawn(const char *swaynag_command,
|
|
struct swaynag_instance *swaynag) {
|
|
if (swaynag->client != NULL) {
|
|
wl_client_destroy(swaynag->client);
|
|
}
|
|
|
|
if (!swaynag_command) {
|
|
return true;
|
|
}
|
|
|
|
if (swaynag->detailed) {
|
|
if (pipe(swaynag->fd) != 0) {
|
|
sway_log(SWAY_ERROR, "Failed to create pipe for swaynag");
|
|
return false;
|
|
}
|
|
if (!sway_set_cloexec(swaynag->fd[1], true)) {
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
int sockets[2];
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) {
|
|
sway_log_errno(SWAY_ERROR, "socketpair failed");
|
|
goto failed;
|
|
}
|
|
if (!sway_set_cloexec(sockets[0], true) || !sway_set_cloexec(sockets[1], true)) {
|
|
goto failed;
|
|
}
|
|
|
|
swaynag->client = wl_client_create(server.wl_display, sockets[0]);
|
|
if (swaynag->client == NULL) {
|
|
sway_log_errno(SWAY_ERROR, "wl_client_create failed");
|
|
goto failed;
|
|
}
|
|
|
|
swaynag->client_destroy.notify = handle_swaynag_client_destroy;
|
|
wl_client_add_destroy_listener(swaynag->client, &swaynag->client_destroy);
|
|
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
sway_log(SWAY_ERROR, "Failed to create fork for swaynag");
|
|
goto failed;
|
|
} else if (pid == 0) {
|
|
restore_nofile_limit();
|
|
if (!sway_set_cloexec(sockets[1], false)) {
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (swaynag->detailed) {
|
|
close(swaynag->fd[1]);
|
|
dup2(swaynag->fd[0], STDIN_FILENO);
|
|
close(swaynag->fd[0]);
|
|
}
|
|
|
|
char wayland_socket_str[16];
|
|
snprintf(wayland_socket_str, sizeof(wayland_socket_str),
|
|
"%d", sockets[1]);
|
|
setenv("WAYLAND_SOCKET", wayland_socket_str, true);
|
|
|
|
size_t length = strlen(swaynag_command) + strlen(swaynag->args) + 2;
|
|
char *cmd = malloc(length);
|
|
snprintf(cmd, length, "%s %s", swaynag_command, swaynag->args);
|
|
execlp("sh", "sh", "-c", cmd, NULL);
|
|
sway_log_errno(SWAY_ERROR, "execlp failed");
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (swaynag->detailed) {
|
|
if (close(swaynag->fd[0]) != 0) {
|
|
sway_log_errno(SWAY_ERROR, "close failed");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (close(sockets[1]) != 0) {
|
|
sway_log_errno(SWAY_ERROR, "close failed");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
failed:
|
|
if (swaynag->detailed) {
|
|
if (close(swaynag->fd[0]) != 0) {
|
|
sway_log_errno(SWAY_ERROR, "close failed");
|
|
return false;
|
|
}
|
|
if (close(swaynag->fd[1]) != 0) {
|
|
sway_log_errno(SWAY_ERROR, "close failed");
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void swaynag_log(const char *swaynag_command, struct swaynag_instance *swaynag,
|
|
const char *fmt, ...) {
|
|
if (!swaynag_command) {
|
|
return;
|
|
}
|
|
|
|
if (!swaynag->detailed) {
|
|
sway_log(SWAY_ERROR, "Attempting to write to non-detailed swaynag inst");
|
|
return;
|
|
}
|
|
|
|
if (swaynag->client == NULL && !swaynag_spawn(swaynag_command, swaynag)) {
|
|
return;
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
char *str = vformat_str(fmt, args);
|
|
va_end(args);
|
|
if (!str) {
|
|
sway_log(SWAY_ERROR, "Failed to allocate buffer for swaynag log entry.");
|
|
return;
|
|
}
|
|
|
|
write(swaynag->fd[1], str, strlen(str));
|
|
|
|
free(str);
|
|
}
|
|
|
|
void swaynag_show(struct swaynag_instance *swaynag) {
|
|
if (swaynag->detailed && swaynag->client != NULL) {
|
|
close(swaynag->fd[1]);
|
|
}
|
|
}
|
|
|