Skip to content

Commit

Permalink
feat: allow passing G_SPAWN stdio flags to awesome.spawn
Browse files Browse the repository at this point in the history
Fixes: #3865
Currently works by allowing the exact strings "DEV_NULL" or "INHERIT" to
be passed to return_std*.

Signed-off-by: aarondill <[email protected]>
  • Loading branch information
aarondill committed Nov 14, 2024
1 parent fcd23a7 commit fa9b07a
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 9 deletions.
84 changes: 75 additions & 9 deletions spawn.c
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,21 @@ spawn_child_exited(pid_t pid, int status)
*
* @tparam string|table cmd The command to launch.
* @tparam[opt=true] boolean use_sn Use startup-notification?
* @tparam[opt=false] boolean stdin Return a fd for stdin?
* @tparam[opt=false] boolean stdout Return a fd for stdout?
* @tparam[opt=false] boolean stderr Return a fd for stderr?
* @tparam[opt="DEV_NULL"] boolean|string stdin Pass `true` to return a fd for
* stdin. Use `"DEV_NULL"` to redirect to /dev/null, or `"INHERIT"` to inherit
* the parent's stdin. Implementation note: Pre-2.74 glib doesn't support
* *explicit* `DEV_NULL`. When `DEV_NULL` is passed on glib <2.74, Awesome will
* use glib's default behaviour.
* @tparam[opt="INHERIT"] boolean|string stdout Pass `true` to return a fd for
* stdout. Use `"DEV_NULL"` to redirect to /dev/null, or `"INHERIT"` to
* inherit the parent's stdout. Implementation note: Pre-2.74 glib doesn't
* support *explicit* `INHERIT`. When `INHERIT` is passed on glib <2.74,
* Awesome will use glib's default behaviour.
* @tparam[opt="INHERIT"] boolean|string stderr Pass `true` to return a fd for
* stderr. Use `"DEV_NULL"` to redirect to /dev/null, or `"INHERIT"` to
* inherit the parent's stderr. Implementation note: Pre-2.74 glib doesn't
* support *explicit* `INHERIT`. When `INHERIT` is passed on glib <2.74,
* Awesome will use glib's default behaviour.
* @tparam[opt=nil] function exit_callback Function to call on process exit. The
* function arguments will be type of exit ("exit" or "signal") and the exit
* code / the signal number causing process termination.
Expand All @@ -441,12 +453,66 @@ luaA_spawn(lua_State *L)

if(lua_gettop(L) >= 2)
use_sn = luaA_checkboolean(L, 2);
if(lua_gettop(L) >= 3)
return_stdin = luaA_checkboolean(L, 3);
if(lua_gettop(L) >= 4)
return_stdout = luaA_checkboolean(L, 4);
if(lua_gettop(L) >= 5)
return_stderr = luaA_checkboolean(L, 5);
/* Valid values for return_std* are:
* true -> return a fd
* false -> keep glib's default behaviour
* "DEV_NULL" -> use direct output to /dev/null
* "INHERIT" -> use the same fd as the parent
*/
if(lua_gettop(L) >= 3) {
if (lua_isstring(L, 3)) {
const char *str = lua_tostring(L, 3);
if (a_strcmp(str, "DEV_NULL") == 0){
// This is the default behaviour. Compiles to a no-op before 2.74.
#if GLIB_CHECK_VERSION(2, 74, 0)
flags |= G_SPAWN_STDIN_FROM_DEV_NULL;
# endif
} else if (a_strcmp(str, "INHERIT") == 0)
flags |= G_SPAWN_CHILD_INHERITS_STDIN;
else
luaA_typerror(L, 3, "DEV_NULL or INHERIT");
} else if(lua_isboolean(L, 3)) {
return_stdin = lua_toboolean(L, 3);
} else {
luaA_typerror(L, 3, "boolean or string");
}
}
if(lua_gettop(L) >= 4) {
if (lua_isstring(L, 4)) {
const char *str = lua_tostring(L, 4);
if (a_strcmp(str, "DEV_NULL") == 0)
flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
else if (a_strcmp(str, "INHERIT") == 0) {
// This is the default behaviour. Compiles to a no-op before 2.74.
#if GLIB_CHECK_VERSION(2, 74, 0)
flags |= G_SPAWN_CHILD_INHERITS_STDOUT;
# endif
} else
luaA_typerror(L, 4, "DEV_NULL or INHERIT");
} else if(lua_isboolean(L, 4)) {
return_stdout = lua_toboolean(L, 4);
} else {
luaA_typerror(L, 4, "boolean or string");
}
}
if(lua_gettop(L) >= 5) {
if (lua_isstring(L, 5)) {
const char *str = lua_tostring(L, 5);
if (a_strcmp(str, "DEV_NULL") == 0)
flags |= G_SPAWN_STDERR_TO_DEV_NULL;
else if (a_strcmp(str, "INHERIT") == 0) {
// This is the default behaviour. Compiles to a no-op before 2.74.
#if GLIB_CHECK_VERSION(2, 74, 0)
flags |= G_SPAWN_CHILD_INHERITS_STDERR;
# endif
} else
luaA_typerror(L, 5, "DEV_NULL or INHERIT");
} else if(lua_isboolean(L, 5)) {
return_stderr = lua_toboolean(L, 5);
} else {
luaA_typerror(L, 5, "boolean or string");
}
}
if (!lua_isnoneornil(L, 6))
{
luaA_checkfunction(L, 6);
Expand Down
67 changes: 67 additions & 0 deletions tests/test-spawn.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ end

local spawns_done = 0
local async_spawns_done = 0
local io_spawns_done = 0
local exit_yay, exit_snd = nil, nil

-- * Using spawn with array is already covered by the test client.
Expand Down Expand Up @@ -161,6 +162,72 @@ local steps = {
return true
end
end,
-- Test inheriting stdio
function(count)
if count == 1 then
do -- Test that DEV_NULL works and doesn't return a fd
local read_line = false
local pid, _, _, stdout, stderr = awesome.spawn({ 'readlink', '/proc/self/fd/2' },
false, false, true, "DEV_NULL")
assert(type(pid) ~= "string", pid)
assert(stderr == nil)
spawn.read_lines(require("lgi").Gio.UnixInputStream.new(stdout, true),
function(line)
assert(not read_line)
read_line = true
assert(line == "/dev/null", line)
io_spawns_done = io_spawns_done + 1
end, nil, true)
end

do -- Test that INHERIT works and doesn't return a fd
-- Note: if this is /dev/null, this test is useless.
local test_stdin = require('lgi').GLib.file_read_link('/proc/self/fd/0')

local read_line = false
local pid, _, stdin, stdout = awesome.spawn({ 'readlink', '/proc/self/fd/0' },
false, "INHERIT", true, false)
assert(type(pid) ~= "string", pid)
assert(stdin == nil)
spawn.read_lines(require("lgi").Gio.UnixInputStream.new(stdout, true),
function(line)
assert(not read_line)
read_line = true
assert(line == test_stdin, line)
io_spawns_done = io_spawns_done + 1
end, nil, true)
end

do -- Test that false doesn't return a pointer (behavior is untested - GLib defaults)
local pid, _, stdin, stdout, stderr = awesome.spawn({"true"},
false, false, false, false)
assert(type(pid) ~= "string", pid)
assert(stdin == nil)
assert(stdout == nil)
assert(stderr == nil)
end

do -- Test that true returns a pipe
local read_line = false
local pid, _, stdin, stdout, stderr = awesome.spawn({ 'readlink', '/proc/self/fd/0' },
false, true, true, true)
assert(type(pid) ~= "string", pid)
assert(stdin ~= nil)
assert(stdout ~= nil)
assert(stderr ~= nil)
spawn.read_lines(require("lgi").Gio.UnixInputStream.new(stdout, true),
function(line)
assert(not read_line)
read_line = true
assert(line:find("^pipe:%[[0-9]+%]$"), line)
io_spawns_done = io_spawns_done + 1
end, nil, true)
end
end
if io_spawns_done == 3 then
return true
end
end,
-- Test spawn_once
function()
if #client.get() ~= 1 then return end
Expand Down

0 comments on commit fa9b07a

Please sign in to comment.