diff --git a/river/command/spawn.zig b/river/command/spawn.zig
index ced3922..e62bf2f 100644
--- a/river/command/spawn.zig
+++ b/river/command/spawn.zig
@@ -19,6 +19,7 @@ const os = std.os;
const c = @import("../c.zig");
const util = @import("../util.zig");
+const process = @import("../process.zig");
const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig");
@@ -40,7 +41,7 @@ pub fn spawn(
};
if (pid == 0) {
- util.post_fork_pre_execve();
+ process.cleanupChild();
const pid2 = os.fork() catch c._exit(1);
if (pid2 == 0) os.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1);
diff --git a/river/main.zig b/river/main.zig
index da59953..74579bf 100644
--- a/river/main.zig
+++ b/river/main.zig
@@ -27,6 +27,7 @@ const flags = @import("flags");
const c = @import("c.zig");
const util = @import("util.zig");
+const process = @import("process.zig");
const Server = @import("Server.zig");
@@ -98,22 +99,16 @@ pub fn main() anyerror!void {
}
};
+ log.info("river version {s}, initializing server", .{build_options.version});
+
+ process.setup();
+
river_init_wlroots_log(switch (runtime_log_level) {
.debug => .debug,
.info => .info,
.warn, .err => .err,
});
- // Ignore SIGPIPE so we don't get killed when writing to a socket that
- // has had its read end closed by another process.
- const sig_ign = os.Sigaction{
- .handler = .{ .handler = os.SIG.IGN },
- .mask = os.empty_sigset,
- .flags = 0,
- };
- try os.sigaction(os.SIG.PIPE, &sig_ign, null);
-
- log.info("river version {s}, initializing server", .{build_options.version});
try server.init(enable_xwayland);
defer server.deinit();
@@ -126,7 +121,7 @@ pub fn main() anyerror!void {
const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null };
const pid = try os.fork();
if (pid == 0) {
- util.post_fork_pre_execve();
+ process.cleanupChild();
os.execveZ("/bin/sh", &child_args, std.c.environ) catch c._exit(1);
}
util.gpa.free(cmd);
diff --git a/river/process.zig b/river/process.zig
new file mode 100644
index 0000000..27316c1
--- /dev/null
+++ b/river/process.zig
@@ -0,0 +1,79 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2022-2024 The River Developers
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+const std = @import("std");
+const os = std.os;
+
+const c = @import("c.zig");
+
+var original_rlimit: ?os.rlimit = null;
+
+pub fn setup() void {
+ // Ignore SIGPIPE so we don't get killed when writing to a socket that
+ // has had its read end closed by another process.
+ const sig_ign = os.Sigaction{
+ .handler = .{ .handler = os.SIG.IGN },
+ .mask = os.empty_sigset,
+ .flags = 0,
+ };
+ os.sigaction(os.SIG.PIPE, &sig_ign, null) catch unreachable;
+
+ // Most unix systems have a default limit of 1024 file descriptors and it
+ // seems unlikely for this default to be universally raised due to the
+ // broken behavior of select() on fds with value >1024. However, it is
+ // unreasonable to use such a low limit for a process such as river which
+ // uses many fds in its communication with wayland clients and the kernel.
+ //
+ // There is however an advantage to having a relatively low limit: it helps
+ // to catch any fd leaks. Therefore, don't use some crazy high limit that
+ // can never be reached before the system runs out of memory. This can be
+ // raised further if anyone reaches it in practice.
+ if (os.getrlimit(.NOFILE)) |original| {
+ original_rlimit = original;
+ const new: os.rlimit = .{
+ .cur = @min(4096, original.max),
+ .max = original.max,
+ };
+ if (os.setrlimit(.NOFILE, new)) {
+ std.log.info("raised file descriptor limit of the river process to {d}", .{new.cur});
+ } else |_| {
+ std.log.err("setrlimit failed, using system default file descriptor limit of {d}", .{
+ original.cur,
+ });
+ }
+ } else |_| {
+ std.log.err("getrlimit failed, using system default file descriptor limit ", .{});
+ }
+}
+
+pub fn cleanupChild() void {
+ if (c.setsid() < 0) unreachable;
+ if (os.system.sigprocmask(os.SIG.SETMASK, &os.empty_sigset, null) < 0) unreachable;
+
+ const sig_dfl = os.Sigaction{
+ .handler = .{ .handler = os.SIG.DFL },
+ .mask = os.empty_sigset,
+ .flags = 0,
+ };
+ os.sigaction(os.SIG.PIPE, &sig_dfl, null) catch unreachable;
+
+ if (original_rlimit) |original| {
+ os.setrlimit(.NOFILE, original) catch {
+ std.log.err("failed to restore original file descriptor limit for " ++
+ "child process, setrlimit failed", .{});
+ };
+ }
+}
diff --git a/river/util.zig b/river/util.zig
index 62e7e97..efbaf34 100644
--- a/river/util.zig
+++ b/river/util.zig
@@ -15,22 +15,6 @@
// along with this program. If not, see .
const std = @import("std");
-const os = std.os;
-
-const xkb = @import("xkbcommon");
-
-const c = @import("c.zig");
/// The global general-purpose allocator used throughout river's code
pub const gpa = std.heap.c_allocator;
-
-pub fn post_fork_pre_execve() void {
- if (c.setsid() < 0) unreachable;
- if (os.system.sigprocmask(os.SIG.SETMASK, &os.empty_sigset, null) < 0) unreachable;
- const sig_dfl = os.Sigaction{
- .handler = .{ .handler = os.SIG.DFL },
- .mask = os.empty_sigset,
- .flags = 0,
- };
- os.sigaction(os.SIG.PIPE, &sig_dfl, null) catch @panic("sigaction before fork failed");
-}