From 21802a3a5892b1bc3d6269657fb0e0cf939fec2a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Fri, 20 Feb 2026 18:56:13 +0800 Subject: [PATCH] opt: spawn use double fork --- src/common/util.c | 35 ++++++++++++-- src/common/util.h | 4 +- src/dispatch/bind_define.h | 98 +++++++++++++++++++++++++++----------- 3 files changed, 104 insertions(+), 33 deletions(-) diff --git a/src/common/util.c b/src/common/util.c index 8e562b19..fc8596b2 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -1,12 +1,14 @@ /* See LICENSE.dwm file for copyright and license details. */ +#include "util.h" #include +#include #include #include #include #include -#include - -#include "util.h" +#include +#include +#include #define PCRE2_CODE_UNIT_WIDTH 8 #include @@ -205,4 +207,31 @@ void wl_list_swap(struct wl_list *l1, struct wl_list *l2) { tmp2_prev->next = l1; tmp2_next->prev = l1; } +} +void restore_nofile_limit(void) { + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { + // Restore the soft limit to the hard limit + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_NOFILE, &rl) != 0) { + wlr_log(WLR_DEBUG, "restore_nofile_limit: setrlimit failed"); + } + } else { + wlr_log(WLR_DEBUG, "restore_nofile_limit: getrlimit failed"); + } +} + +void reset_child_environment(void) { + restore_nofile_limit(); + + setsid(); + + // Reset signal handlers + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); + + // Reset signal handlers to default + signal(SIGPIPE, SIG_DFL); + signal(SIGCHLD, SIG_DFL); } \ No newline at end of file diff --git a/src/common/util.h b/src/common/util.h index c7f83f2b..11671183 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -1,5 +1,6 @@ /* See LICENSE.dwm file for copyright and license details. */ #include +#include void die(const char *fmt, ...); void *ecalloc(size_t nmemb, size_t size); @@ -12,4 +13,5 @@ char *join_strings(char *arr[], const char *sep); char *join_strings_with_suffix(char *arr[], const char *suffix, const char *sep); char *string_printf(const char *fmt, ...); -void wl_list_swap(struct wl_list *l1, struct wl_list *l2); \ No newline at end of file +void wl_list_swap(struct wl_list *l1, struct wl_list *l2); +void reset_child_environment(void); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 61a1a2a5..a6bd9f1b 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -907,22 +907,40 @@ int32_t spawn_shell(const Arg *arg) { if (!arg->v) return 0; - if (fork() == 0) { - signal(SIGSEGV, SIG_IGN); - signal(SIGABRT, SIG_IGN); - signal(SIGILL, SIG_IGN); - - dup2(STDERR_FILENO, STDOUT_FILENO); - setsid(); - - execlp("sh", "sh", "-c", arg->v, (char *)NULL); - execlp("bash", "bash", "-c", arg->v, (char *)NULL); - - wlr_log(WLR_DEBUG, - "mango: failed to execute command '%s' with shell: %s\n", - arg->v, strerror(errno)); - _exit(EXIT_FAILURE); + pid_t child = fork(); + if (child == -1) { + wlr_log_errno(WLR_ERROR, "spawn_shell: first fork failed"); + return 0; } + + if (child == 0) { + // 重置子进程环境与信号 + reset_child_environment(); + + pid_t grandchild = fork(); + if (grandchild == -1) { + wlr_log_errno(WLR_ERROR, "spawn_shell: second fork failed"); + _exit(EXIT_FAILURE); + } + + if (grandchild == 0) { + // 孙进程负责实际执行 + execlp("sh", "sh", "-c", arg->v, (char *)NULL); + + // fallback to bash + execlp("bash", "bash", "-c", arg->v, (char *)NULL); + + // 如果两个 shell 都启动失败 + wlr_log_errno(WLR_ERROR, "spawn_shell: execlp failed for '%s'", + (char *)arg->v); + _exit(EXIT_FAILURE); + } + + // 子进程创建完孙进程后立即退出,主进程的 waitpid 瞬间返回 + _exit(EXIT_SUCCESS); + } + + waitpid(child, NULL, 0); return 0; } @@ -930,27 +948,49 @@ int32_t spawn(const Arg *arg) { if (!arg->v) return 0; - if (fork() == 0) { - signal(SIGSEGV, SIG_IGN); - signal(SIGABRT, SIG_IGN); - signal(SIGILL, SIG_IGN); + pid_t child = fork(); + if (child == -1) { + wlr_log_errno(WLR_ERROR, "spawn: first fork failed"); + return 0; + } - dup2(STDERR_FILENO, STDOUT_FILENO); - setsid(); + if (child == 0) { + // 重置子进程环境与信号 + reset_child_environment(); - wordexp_t p; - if (wordexp(arg->v, &p, 0) != 0) { - wlr_log(WLR_DEBUG, "mango: wordexp failed for '%s'\n", arg->v); + // create second child + pid_t grandchild = fork(); + if (grandchild == -1) { + wlr_log_errno(WLR_ERROR, "spawn: second fork failed"); _exit(EXIT_FAILURE); } - execvp(p.we_wordv[0], p.we_wordv); + if (grandchild == 0) { + // execute actual command + wordexp_t p; - wlr_log(WLR_DEBUG, "mango: execvp '%s' failed: %s\n", p.we_wordv[0], - strerror(errno)); - wordfree(&p); - _exit(EXIT_FAILURE); + if (wordexp(arg->v, &p, 0) != 0) { + wlr_log(WLR_ERROR, "mango: wordexp failed for '%s'", + (char *)arg->v); + _exit(EXIT_FAILURE); + } + + // 执行成功的话,当前进程映像会被替换,自动释放堆内存 + execvp(p.we_wordv[0], p.we_wordv); + + // 只有 execvp 失败时才会走到这里 + wlr_log_errno(WLR_ERROR, "spawn: execvp '%s' failed", + p.we_wordv[0]); + + wordfree(&p); + _exit(EXIT_FAILURE); + } + + // 子进程退出,孙进程交由 init (systemd) 托管 + _exit(EXIT_SUCCESS); } + + waitpid(child, NULL, 0); return 0; }