From fa9b07a735eabb203c76ec09fc4f8cd697027721 Mon Sep 17 00:00:00 2001 From: aarondill Date: Sat, 29 Jun 2024 23:01:36 -0500 Subject: [PATCH] feat: allow passing G_SPAWN stdio flags to awesome.spawn Fixes: #3865 Currently works by allowing the exact strings "DEV_NULL" or "INHERIT" to be passed to return_std*. Signed-off-by: aarondill --- spawn.c | 84 +++++++++++++++++++++++++++++++++++++++----- tests/test-spawn.lua | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 9 deletions(-) diff --git a/spawn.c b/spawn.c index d736d5834f..c0a21d4860 100644 --- a/spawn.c +++ b/spawn.c @@ -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. @@ -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); diff --git a/tests/test-spawn.lua b/tests/test-spawn.lua index 71b901929c..dfc3cc5f8d 100644 --- a/tests/test-spawn.lua +++ b/tests/test-spawn.lua @@ -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. @@ -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