From 33fb7725c5a9d68d28bc2a30537210518aef3486 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sun, 21 Feb 2021 22:03:03 +0100 Subject: [PATCH] river: send SIGTERM to init command process group Run the init command in a new process group and send SIGTERM to the entire group on exit. Without doing this, only the sh invocation used for the `sh -c` would receive SIGTERM. This is particularly useful when starting a per-session server manager as the init command. --- doc/river.1.scd | 7 +++---- river/main.zig | 50 ++++++++++++++++++++++++------------------------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/doc/river.1.scd b/doc/river.1.scd index 9ca0c43..bd38fa1 100644 --- a/doc/river.1.scd +++ b/doc/river.1.scd @@ -39,10 +39,9 @@ following locations, checked in the order listed: - $HOME/.config/river/init - /etc/river/init -This executable init file will be run after river's wayland server is -initialized but before entering the main loop. If the process started by -this flag is still running when river exits, river will send SIGTERM and -and wait for it to terminate. +The executable init file will be run as a process group leader after river's +wayland server is initialized but before entering the main loop. On exit, +river will send SIGTERM to this process group. Usually this will be a shell script invoking *riverctl*(1) to create mappings, start programs such as a status bar, and preform other configuration. diff --git a/river/main.zig b/river/main.zig index 803cd7b..20529b8 100644 --- a/river/main.zig +++ b/river/main.zig @@ -16,6 +16,7 @@ // along with this program. If not, see . const std = @import("std"); +const os = std.os; const wlr = @import("wlroots"); const build_options = @import("build_options"); @@ -45,7 +46,7 @@ const usage: []const u8 = fn testConfigPath(comptime fmt: []const u8, args: anytype) std.fmt.AllocPrintError!?[:0]const u8 { const path = try std.fmt.allocPrintZ(util.gpa, fmt, args); - std.os.access(path, std.os.X_OK) catch { + os.access(path, os.X_OK) catch { util.gpa.free(path); return null; }; @@ -53,11 +54,11 @@ fn testConfigPath(comptime fmt: []const u8, args: anytype) std.fmt.AllocPrintErr } fn getStartupCommand() std.fmt.AllocPrintError!?[:0]const u8 { - if (std.os.getenv("XDG_CONFIG_HOME")) |xdg_config_home| { + if (os.getenv("XDG_CONFIG_HOME")) |xdg_config_home| { if (try testConfigPath("{}/river/init", .{xdg_config_home})) |path| { return path; } - } else if (std.os.getenv("HOME")) |home| { + } else if (os.getenv("HOME")) |home| { if (try testConfigPath("{}/.config/river/init", .{home})) |path| { return path; } @@ -94,13 +95,13 @@ pub fn main() anyerror!void { if (std.mem.eql(u8, arg, "-h")) { const stdout = std.io.getStdOut().outStream(); try stdout.print(usage, .{}); - std.os.exit(0); + os.exit(0); } else if (std.mem.eql(u8, arg, "-c")) { if (it.nextPosix()) |command| { // If the user used '-c' multiple times the variable // already holds a path and needs to be freed. - if (startup_command) |ptr| util.gpa.free(ptr); - startup_command = try util.gpa.dupeZ(u8, std.mem.spanZ(command.ptr)); + if (startup_command) |cmd| util.gpa.free(cmd); + startup_command = try util.gpa.dupeZ(u8, command); } else { printErrorExit("Error: flag '-c' requires exactly one argument", .{}); } @@ -115,7 +116,7 @@ pub fn main() anyerror!void { } else { const stderr = std.io.getStdErr().outStream(); try stderr.print(usage, .{}); - std.os.exit(1); + os.exit(1); } } } @@ -126,39 +127,36 @@ pub fn main() anyerror!void { .warn, .err, .crit, .alert, .emerg => .err, }); - log_server.info("initializing", .{}); - if (startup_command == null) { - if (try getStartupCommand()) |path| { - startup_command = path; - log_server.info("Using default startup command path: {}", .{path}); - } else { - log_server.info("Starting without startup command", .{}); - } - } else { - log_server.info("Using custom startup command path: {}", .{startup_command}); + if (try getStartupCommand()) |path| startup_command = path; } + log_server.info("initializing server", .{}); var server: Server = undefined; try server.init(); defer server.deinit(); try server.start(); - const child_pid = if (startup_command) |cmd| blk: { + // Run the child in a new process group so that we can send SIGTERM to all + // descendants on exit. + const child_pgid = if (startup_command) |cmd| blk: { + log_server.info("running startup command '{}'", .{cmd}); const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null }; - const pid = try std.os.fork(); + const pid = try os.fork(); if (pid == 0) { - if (std.os.system.sigprocmask(std.os.SIG_SETMASK, &std.os.empty_sigset, null) < 0) unreachable; - std.os.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1); + if (c.setsid() < 0) unreachable; + if (os.system.sigprocmask(os.SIG_SETMASK, &os.empty_sigset, null) < 0) unreachable; + os.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1); } util.gpa.free(cmd); + // Since the child has called setsid, the pid is the pgid break :blk pid; } else null; - defer if (child_pid) |pid| - std.os.kill(pid, std.os.SIGTERM) catch |e| log_server.err("failed to kill startup process: {}", .{e}); + defer if (child_pgid) |pgid| + os.kill(-pgid, os.SIGTERM) catch |e| log_server.err("failed to kill startup process: {}", .{e}); - log_server.info("running...", .{}); + log_server.info("running server", .{}); server.wl_server.run(); @@ -167,6 +165,6 @@ pub fn main() anyerror!void { fn printErrorExit(comptime format: []const u8, args: anytype) noreturn { const stderr = std.io.getStdErr().outStream(); - stderr.print(format ++ "\n", args) catch std.os.exit(1); - std.os.exit(1); + stderr.print(format ++ "\n", args) catch os.exit(1); + os.exit(1); }