diff --git a/spawn.c b/spawn.c index e152559b..da2afdcc 100644 --- a/spawn.c +++ b/spawn.c @@ -369,16 +369,57 @@ spawn_callback(gpointer user_data) unsetenv("DESKTOP_STARTUP_ID"); } +/** Convert a Lua table of strings to a char** array. + * \param L The Lua VM state. + * \param idx The index of the table that we should parse. + * \return The argv array. + */ +static gchar ** +parse_table_array(lua_State *L, int idx, GError **error) +{ + gchar **argv = NULL; + size_t i, len; + + luaL_checktype(L, idx, LUA_TTABLE); + idx = luaA_absindex(L, idx); + len = luaA_rawlen(L, idx); + + /* First verify that the table is sane: All integer keys must contain + * strings. Do this by pushing them all onto the stack. + */ + for (i = 0; i < len; i++) + { + lua_rawgeti(L, idx, i+1); + if (lua_type(L, -1) != LUA_TSTRING) + { + g_set_error(error, G_SPAWN_ERROR, 0, + "Non-string argument at table index %zd", i+1); + return NULL; + } + } + + /* From this point on nothing can go wrong and so we can safely allocate + * memory. + */ + argv = g_new0(gchar *, len + 1); + for (i = 0; i < len; i++) + { + argv[len - i - 1] = g_strdup(lua_tostring(L, -1)); + lua_pop(L, 1); + } + + return argv; +} + /** Parse a command line. * \param L The Lua VM state. - * \param idx The index of the argument that we should parse + * \param idx The index of the argument that we should parse. * \return The argv array for the new process. */ static gchar ** parse_command(lua_State *L, int idx, GError **error) { gchar **argv = NULL; - idx = luaA_absindex(L, idx); if (lua_isstring(L, idx)) { @@ -388,31 +429,13 @@ parse_command(lua_State *L, int idx, GError **error) } else if (lua_istable(L, idx)) { - size_t i, len = luaA_rawlen(L, idx); - - /* First verify that the table is sane: All integer keys must contain - * strings. Do this by pushing them all onto the stack. - */ - for (i = 0; i < len; i++) - { - lua_rawgeti(L, idx, i+1); - if (lua_type(L, -1) != LUA_TSTRING) - luaL_error(L, "Non-string argument at table index %d", i+1); - } - - /* From this point on nothing can go wrong and so we can safely allocate - * memory. - */ - argv = g_new0(gchar *, len + 1); - for (i = 0; i < len; i++) - { - argv[len - i - 1] = g_strdup(lua_tostring(L, -1)); - lua_pop(L, 1); - } + argv = parse_table_array(L, idx, error); } else { - luaL_error(L, "Invalid argument to spawn(), expect string or table"); + g_set_error_literal(error, G_SPAWN_ERROR, 0, + "Invalid argument to spawn(), expected string or table"); + return NULL; } return argv; @@ -450,20 +473,22 @@ child_exit_callback(GPid pid, gint status, gpointer user_data) * @tparam[opt=false] boolean stdout Return a fd for stdout? * @tparam[opt=false] boolean stderr Return a fd for stderr? * @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. + * function arguments will be type of exit ("exit" or "signal") and the exit + * code / the signal number causing process termination. + * @tparam[opt=nil] table cmd The environment to use for the spawned program. + * Without this the spawned process inherits awesome's environment. * @treturn[1] integer Process ID if everything is OK. * @treturn[1] string Startup-notification ID, if `use_sn` is true. * @treturn[1] integer stdin, if `stdin` is true. * @treturn[1] integer stdout, if `stdout` is true. * @treturn[1] integer stderr, if `stderr` is true. - * @treturn[2] string An error string if an error occured. + * @treturn[2] string An error string if an error occurred. * @function spawn */ int luaA_spawn(lua_State *L) { - gchar **argv = NULL; + gchar **argv = NULL, **envp = NULL; bool use_sn = true, return_stdin = false, return_stdout = false, return_stderr = false; int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1; int *stdin_ptr = NULL, *stdout_ptr = NULL, *stderr_ptr = NULL; @@ -505,6 +530,17 @@ luaA_spawn(lua_State *L) return 1; } + if (!lua_isnoneornil(L, 7)) { + envp = parse_table_array(L, 7, &error); + if (error) { + g_strfreev(argv); + g_strfreev(envp); + lua_pushfstring(L, "spawn: environment parse error: %s", error->message); + g_error_free(error); + return 1; + } + } + SnLauncherContext *context = NULL; if(use_sn) { @@ -520,10 +556,11 @@ luaA_spawn(lua_State *L) } flags |= G_SPAWN_SEARCH_PATH | G_SPAWN_CLOEXEC_PIPES; - retval = g_spawn_async_with_pipes(NULL, argv, NULL, flags, + retval = g_spawn_async_with_pipes(NULL, argv, envp, flags, spawn_callback, context, &pid, stdin_ptr, stdout_ptr, stderr_ptr, &error); g_strfreev(argv); + g_strfreev(envp); if(!retval) { lua_pushstring(L, error->message); diff --git a/tests/test-spawn.lua b/tests/test-spawn.lua index 21c6c8f4..47758b3c 100644 --- a/tests/test-spawn.lua +++ b/tests/test-spawn.lua @@ -71,6 +71,28 @@ local steps = { end }) + -- Test that setting env vars works and that the env is cleared + local read_line = false + local pid, _, _, stdout = awesome.spawn({ "sh", "-c", "echo $AWESOME_SPAWN_TEST_VAR $HOME $USER" }, + false, false, true, false, nil, { "AWESOME_SPAWN_TEST_VAR=42" }) + assert(type(pid) ~= "string", pid) + spawn.read_lines(require("lgi").Gio.UnixInputStream.new(stdout, true), + function(line) + assert(not read_line) + read_line = true + assert(line == "42", line) + spawns_done = spawns_done + 1 + end, nil, true) + + -- Test error in parse_table_array. + pid = awesome.spawn({"true"}, false, false, true, false, nil, { 0 }) + assert(pid == 'spawn: environment parse error: Non-string argument at table index 1', pid) + + -- Test error in parse_command. + pid = awesome.spawn({0}, false, false, true, false, nil, {}) + assert(pid == 'spawn: parse error: Non-string argument at table index 1', pid) + + local steps_count = 0 local err_count = 0 spawn.with_line_callback({ "sh", "-c", "printf line1\\\\nline2\\\\nline3 ; echo err >&2 ; exit 42" }, { @@ -98,7 +120,7 @@ local steps = { end }) end - if spawns_done == 2 then + if spawns_done == 3 then assert(exit_yay == 0) assert(exit_snd == 42) assert(async_spawns_done == 2)