river: make spawn command take only one argument

Currently the spawn command takes any number of arguments and naively
joins them together with spaces before passing them as the single
argument of `/bin/sh -c`. This however produces unexpected results as
soon as shell quoting gets involved in the arguments passed to spawn.
For example, running

riverctl spawn foo "bar baz"

will execute `/bin/sh -c "foo bar baz"`, unexpectedly splitting bar and
baz into separate arguments. To avoid this confusion, make the spawn
command take only a single argument, forcing the user to quote properly
to spawn multi-argument commands.
This commit is contained in:
Isaac Freund 2021-07-16 00:00:28 +02:00
parent dfa2471141
commit b7e15a8ef6
No known key found for this signature in database
GPG key ID: 86DED400DDFD7A11
3 changed files with 17 additions and 19 deletions

View file

@ -65,9 +65,9 @@ over the Wayland protocol.
output in any direction. output in any direction.
*spawn* _shell_command_ *spawn* _shell_command_
Run _shell_command_ using _/bin/sh -c_. Put single quotes around Run _shell_command_ using `/bin/sh -c _shell_command_`. Note that
_shell_command_ if you do not want special characters to get *spawn* only takes a single argument. To spawn a command taking
interpreted by your shell before the command gets passed to _/bin/sh_. multiple arguments, wrapping the command in quotes is recommended.
*swap* *next*|*previous* *swap* *next*|*previous*
Swap the focused view with the next/previous visible non-floating Swap the focused view with the next/previous visible non-floating
@ -336,9 +336,9 @@ However note that not every input device supports every property.
# EXAMPLES # EXAMPLES
Bind bemenu-run to Super+P in normal mode: Bind Super+Enter in normal mode to spawn a *foot*(1) terminal:
riverctl map normal Mod4 P spawn bemenu-run riverctl map normal Mod4 Enter spawn 'foot --app-id=foobar'
See also the example init script at /etc/river/init. See also the example init script at /etc/river/init.

View file

@ -122,22 +122,22 @@ riverctl map passthrough $mod F11 enter-mode normal
for mode in normal locked for mode in normal locked
do do
# Eject the optical drive # Eject the optical drive
riverctl map $mode None XF86Eject spawn eject -T riverctl map $mode None XF86Eject spawn 'eject -T'
# Control pulse audio volume with pamixer (https://github.com/cdemoulins/pamixer) # Control pulse audio volume with pamixer (https://github.com/cdemoulins/pamixer)
riverctl map $mode None XF86AudioRaiseVolume spawn pamixer -i 5 riverctl map $mode None XF86AudioRaiseVolume spawn 'pamixer -i 5'
riverctl map $mode None XF86AudioLowerVolume spawn pamixer -d 5 riverctl map $mode None XF86AudioLowerVolume spawn 'pamixer -d 5'
riverctl map $mode None XF86AudioMute spawn pamixer --toggle-mute riverctl map $mode None XF86AudioMute spawn 'pamixer --toggle-mute'
# Control MPRIS aware media players with playerctl (https://github.com/altdesktop/playerctl) # Control MPRIS aware media players with playerctl (https://github.com/altdesktop/playerctl)
riverctl map $mode None XF86AudioMedia spawn playerctl play-pause riverctl map $mode None XF86AudioMedia spawn 'playerctl play-pause'
riverctl map $mode None XF86AudioPlay spawn playerctl play-pause riverctl map $mode None XF86AudioPlay spawn 'playerctl play-pause'
riverctl map $mode None XF86AudioPrev spawn playerctl previous riverctl map $mode None XF86AudioPrev spawn 'playerctl previous'
riverctl map $mode None XF86AudioNext spawn playerctl next riverctl map $mode None XF86AudioNext spawn 'playerctl next'
# Control screen backlight brighness with light (https://github.com/haikarainen/light) # Control screen backlight brighness with light (https://github.com/haikarainen/light)
riverctl map $mode None XF86MonBrightnessUp spawn light -A 5 riverctl map $mode None XF86MonBrightnessUp spawn 'light -A 5'
riverctl map $mode None XF86MonBrightnessDown spawn light -U 5 riverctl map $mode None XF86MonBrightnessDown spawn 'light -U 5'
done done
# Set repeat rate # Set repeat rate

View file

@ -30,11 +30,9 @@ pub fn spawn(
out: *?[]const u8, out: *?[]const u8,
) Error!void { ) Error!void {
if (args.len < 2) return Error.NotEnoughArguments; if (args.len < 2) return Error.NotEnoughArguments;
if (args.len > 2) return Error.TooManyArguments;
const cmd = try std.mem.joinZ(allocator, " ", args[1..]); const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", args[1], null };
defer allocator.free(cmd);
const child_args = [_:null]?[*:0]const u8{ "/bin/sh", "-c", cmd, null };
const pid = std.os.fork() catch { const pid = std.os.fork() catch {
out.* = try std.fmt.allocPrint(allocator, "fork/execve failed", .{}); out.* = try std.fmt.allocPrint(allocator, "fork/execve failed", .{});