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.
This commit is contained in:
Isaac Freund 2021-02-21 22:03:03 +01:00
parent f72656b72e
commit 33fb7725c5
2 changed files with 27 additions and 30 deletions

View file

@ -39,10 +39,9 @@ following locations, checked in the order listed:
- $HOME/.config/river/init - $HOME/.config/river/init
- /etc/river/init - /etc/river/init
This executable init file will be run after river's wayland server is The executable init file will be run as a process group leader after river's
initialized but before entering the main loop. If the process started by wayland server is initialized but before entering the main loop. On exit,
this flag is still running when river exits, river will send SIGTERM and river will send SIGTERM to this process group.
and wait for it to terminate.
Usually this will be a shell script invoking *riverctl*(1) to create mappings, Usually this will be a shell script invoking *riverctl*(1) to create mappings,
start programs such as a status bar, and preform other configuration. start programs such as a status bar, and preform other configuration.

View file

@ -16,6 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const os = std.os;
const wlr = @import("wlroots"); const wlr = @import("wlroots");
const build_options = @import("build_options"); 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 { fn testConfigPath(comptime fmt: []const u8, args: anytype) std.fmt.AllocPrintError!?[:0]const u8 {
const path = try std.fmt.allocPrintZ(util.gpa, fmt, args); 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); util.gpa.free(path);
return null; 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 { 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| { if (try testConfigPath("{}/river/init", .{xdg_config_home})) |path| {
return path; return path;
} }
} else if (std.os.getenv("HOME")) |home| { } else if (os.getenv("HOME")) |home| {
if (try testConfigPath("{}/.config/river/init", .{home})) |path| { if (try testConfigPath("{}/.config/river/init", .{home})) |path| {
return path; return path;
} }
@ -94,13 +95,13 @@ pub fn main() anyerror!void {
if (std.mem.eql(u8, arg, "-h")) { if (std.mem.eql(u8, arg, "-h")) {
const stdout = std.io.getStdOut().outStream(); const stdout = std.io.getStdOut().outStream();
try stdout.print(usage, .{}); try stdout.print(usage, .{});
std.os.exit(0); os.exit(0);
} else if (std.mem.eql(u8, arg, "-c")) { } else if (std.mem.eql(u8, arg, "-c")) {
if (it.nextPosix()) |command| { if (it.nextPosix()) |command| {
// If the user used '-c' multiple times the variable // If the user used '-c' multiple times the variable
// already holds a path and needs to be freed. // already holds a path and needs to be freed.
if (startup_command) |ptr| util.gpa.free(ptr); if (startup_command) |cmd| util.gpa.free(cmd);
startup_command = try util.gpa.dupeZ(u8, std.mem.spanZ(command.ptr)); startup_command = try util.gpa.dupeZ(u8, command);
} else { } else {
printErrorExit("Error: flag '-c' requires exactly one argument", .{}); printErrorExit("Error: flag '-c' requires exactly one argument", .{});
} }
@ -115,7 +116,7 @@ pub fn main() anyerror!void {
} else { } else {
const stderr = std.io.getStdErr().outStream(); const stderr = std.io.getStdErr().outStream();
try stderr.print(usage, .{}); 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, .warn, .err, .crit, .alert, .emerg => .err,
}); });
log_server.info("initializing", .{});
if (startup_command == null) { if (startup_command == null) {
if (try getStartupCommand()) |path| { if (try getStartupCommand()) |path| startup_command = 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});
} }
log_server.info("initializing server", .{});
var server: Server = undefined; var server: Server = undefined;
try server.init(); try server.init();
defer server.deinit(); defer server.deinit();
try server.start(); 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 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 (pid == 0) {
if (std.os.system.sigprocmask(std.os.SIG_SETMASK, &std.os.empty_sigset, null) < 0) unreachable; if (c.setsid() < 0) unreachable;
std.os.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1); 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); util.gpa.free(cmd);
// Since the child has called setsid, the pid is the pgid
break :blk pid; break :blk pid;
} else null; } else null;
defer if (child_pid) |pid| defer if (child_pgid) |pgid|
std.os.kill(pid, std.os.SIGTERM) catch |e| log_server.err("failed to kill startup process: {}", .{e}); 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(); server.wl_server.run();
@ -167,6 +165,6 @@ pub fn main() anyerror!void {
fn printErrorExit(comptime format: []const u8, args: anytype) noreturn { fn printErrorExit(comptime format: []const u8, args: anytype) noreturn {
const stderr = std.io.getStdErr().outStream(); const stderr = std.io.getStdErr().outStream();
stderr.print(format ++ "\n", args) catch std.os.exit(1); stderr.print(format ++ "\n", args) catch os.exit(1);
std.os.exit(1); os.exit(1);
} }