diff --git a/man/launch.1.md b/man/launch.1.md index 4e6d52d863f7b3a4f5086f8bf95454629547fdbc..bc5aaa4a18af7c2fb338170b4a2a80ef54675cdf 100644 --- a/man/launch.1.md +++ b/man/launch.1.md @@ -14,6 +14,9 @@ pressure-vessel-launch - client to launch processes in a container [**--directory** *DIR*] [**--env** _VAR_**=**_VALUE_] [**--forward-fd** *FD*] +[**--pass-env** *VAR*] +[**--pass-env-matching** *WILDCARD*] +[**--unset-env** *VAR*] [**--verbose**] {**--bus-name** *NAME*|**--dbus-address** *ADDRESS*|**--socket** *SOCKET*} [**--**] @@ -54,21 +57,16 @@ as a subprocess of **pressure-vessel-launcher**. **--clear-env** : The *COMMAND* runs in an empty environment, apart from any environment - variables set by **--env**. By default, it inherits environment - variables from **pressure-vessel-launcher**, with **--env** - overriding individual variables. + variables set by **--env** and similar options. + By default, it inherits environment variables from + **pressure-vessel-launcher**, with **--env** and + similar options overriding or unsetting individual variables. **--directory** *DIR* : Arrange for the *COMMAND* to run in *DIR*. By default, it inherits the current working directory from **pressure-vessel-launcher**. -**--env** _VAR=VALUE_ -: Set environment variable _VAR_ to _VALUE_. - This is mostly equivalent to using - **env** _VAR=VALUE_ *COMMAND* *ARGUMENTS...* - as the command. - **--forward-fd** *FD* : Arrange for the *COMMAND* to receive file descriptor number *FD* from outside the container. File descriptors 0, 1 and 2 @@ -81,6 +79,41 @@ as a subprocess of **pressure-vessel-launcher**. **--verbose** : Be more verbose. +# ENVIRONMENT OPTIONS + +Options from this group are processed in order, with each option taking +precedence over any earlier options that affect the same environment variable. +For example, +**--pass-env-matching="FO*" --env=FOO=bar --unset-env=FOCUS** +will set **FOO** to **bar**, unset **FOCUS** even if the caller has +it set, and pass through **FONTS** from the caller. + +**--env** _VAR=VALUE_ +: Set environment variable _VAR_ to _VALUE_. + This is mostly equivalent to using + **env** _VAR=VALUE_ *COMMAND* *ARGUMENTS...* + as the command. + +**--pass-env** *VAR* +: If the environment variable *VAR* is set, pass its current value + into the container as if via **--env**. Otherwise, unset it as if + via **--unset-env**. + +**--pass-env-matching** *WILDCARD* +: For each environment variable that is set and has a name matching + the **fnmatch**(3) pattern *WILDCARD*, pass its current value + into the container as if via **--env**. + For example, **--pass-env-matching=Steam*** copies Steam-related + environment variables. + If this command is run from a shell, the wildcard will usually need + to be quoted, for example **--pass-env-matching="Steam*"**. + +**--unset-env** *VAR* +: Unset *VAR* when running the command. + This is mostly equivalent to using + **env -u** *VAR* *COMMAND* *ARGUMENTS...* + as the command. + # OUTPUT The standard output from *COMMAND* is printed on standard output. diff --git a/src/launch.c b/src/launch.c index 812a31e9bdc40082013cd649ab1941120008232f..9407d1fd10db774a9a4e66adac44f5819d1c9596 100644 --- a/src/launch.c +++ b/src/launch.c @@ -28,6 +28,7 @@ #include "config.h" #include "subprojects/libglnx/config.h" +#include <fnmatch.h> #include <locale.h> #include <sysexits.h> #include <sys/signalfd.h> @@ -291,11 +292,99 @@ static gboolean opt_clear_env = FALSE; static gchar *opt_dbus_address = NULL; static gchar *opt_directory = NULL; static gchar *opt_socket = NULL; -static gchar **opt_envs = NULL; +static GHashTable *opt_env = NULL; +static GHashTable *opt_unsetenv = NULL; static gboolean opt_terminate = FALSE; static gboolean opt_verbose = FALSE; static gboolean opt_version = FALSE; +static gboolean +opt_env_cb (const char *option_name, + const gchar *value, + G_GNUC_UNUSED gpointer data, + GError **error) +{ + g_assert (opt_env != NULL); + g_assert (opt_unsetenv != NULL); + + if (g_strcmp0 (option_name, "--env") == 0) + { + g_auto(GStrv) split = g_strsplit (value, "=", 2); + + if (split == NULL || + split[0] == NULL || + split[0][0] == 0 || + split[1] == NULL) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Invalid env format %s", value); + return FALSE; + } + + g_hash_table_remove (opt_unsetenv, split[0]); + g_hash_table_replace (opt_env, + g_steal_pointer (&split[0]), + g_steal_pointer (&split[1])); + return TRUE; + } + + if (g_strcmp0 (option_name, "--pass-env") == 0) + { + const gchar *env = g_getenv (value); + + if (env != NULL) + { + g_hash_table_remove (opt_unsetenv, value); + g_hash_table_replace (opt_env, g_strdup (value), g_strdup (env)); + } + else + { + g_hash_table_remove (opt_env, value); + g_hash_table_add (opt_unsetenv, g_strdup (value)); + } + + return TRUE; + } + + if (g_strcmp0 (option_name, "--pass-env-matching") == 0) + { + char **iter; + + if (environ == NULL) + return TRUE; + + for (iter = environ; *iter != NULL; iter++) + { + g_auto(GStrv) split = g_strsplit (*iter, "=", 2); + + if (split == NULL || + split[0] == NULL || + split[0][0] == 0 || + split[1] == NULL) + continue; + + if (fnmatch (value, split[0], 0) == 0) + { + g_hash_table_remove (opt_unsetenv, split[0]); + g_hash_table_replace (opt_env, + g_steal_pointer (&split[0]), + g_steal_pointer (&split[1])); + } + } + + return TRUE; + } + + if (g_strcmp0 (option_name, "--unset-env") == 0) + { + g_hash_table_remove (opt_env, value); + g_hash_table_add (opt_unsetenv, g_strdup (value)); + return TRUE; + } + + g_return_val_if_reached (FALSE); +} + static const GOptionEntry options[] = { { "bus-name", '\0', @@ -313,13 +402,20 @@ static const GOptionEntry options[] = G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_directory, "Working directory in which to run the command.", "DIR" }, { "env", '\0', - G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &opt_envs, + G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, opt_env_cb, "Set environment variable.", "VAR=VALUE" }, { "forward-fd", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &forward_fds, "Connect a file descriptor to the launched process. " "fds 0, 1 and 2 are automatically forwarded.", "FD" }, + { "pass-env", '\0', + G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, opt_env_cb, + "Pass environment variable through, or unset if set.", "VAR" }, + { "pass-env-matching", '\0', + G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, opt_env_cb, + "Pass environment variables matching a shell-style wildcard.", + "WILDCARD" }, { "socket", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &opt_socket, "Connect to a Launcher server listening on this AF_UNIX socket.", @@ -327,6 +423,9 @@ static const GOptionEntry options[] = { "terminate", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_terminate, "Terminate the Launcher server.", NULL }, + { "unset-env", '\0', + G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, opt_env_cb, + "Unset environment variable, like env -u.", "VAR" }, { "verbose", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_verbose, "Be more verbose.", NULL }, @@ -353,6 +452,7 @@ main (int argc, g_autoptr(GOptionContext) context = NULL; g_autoptr(GError) local_error = NULL; GError **error = &local_error; + g_autoptr(GPtrArray) env_prefix = NULL; char **command_and_args; guint forward_signals_id = 0; g_autoptr(FILE) original_stdout = NULL; @@ -369,6 +469,8 @@ main (int argc, PvLaunchFlags spawn_flags = 0; guint signal_source = 0; gsize i; + GHashTableIter iter; + gpointer key, value; setlocale (LC_ALL, ""); @@ -384,6 +486,8 @@ main (int argc, "processes."); g_option_context_add_main_entries (context, options, NULL); + opt_env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + opt_unsetenv = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); opt_verbose = pv_boolean_environment ("PRESSURE_VESSEL_VERBOSE", FALSE); if (!g_option_context_parse (context, &argc, &argv, error)) @@ -633,18 +737,10 @@ main (int argc, g_variant_builder_add (&fd_builder, "{uh}", fd, handle); } - for (i = 0; opt_envs != NULL && opt_envs[i] != NULL; i++) - { - const char *opt_env = opt_envs[i]; - g_auto(GStrv) split = g_strsplit (opt_env, "=", 2); + g_hash_table_iter_init (&iter, opt_env); - if (split == NULL || split[0] == NULL || split[0][0] == 0 || split[1] == NULL) - { - glnx_throw (error, "Invalid env format %s", opt_env); - goto out; - } - g_variant_builder_add (&env_builder, "{ss}", split[0], split[1]); - } + while (g_hash_table_iter_next (&iter, &key, &value)) + g_variant_builder_add (&env_builder, "{ss}", key, value); spawn_flags = 0; @@ -671,11 +767,49 @@ main (int argc, g_main_loop_ref (loop), (GDestroyNotify) g_main_loop_unref); + /* Prepend "env -u" if necessary */ + if (g_hash_table_size (opt_unsetenv) > 0) + { + env_prefix = g_ptr_array_new_full (2 * g_hash_table_size (opt_unsetenv) + 6, + g_free); + g_ptr_array_add (env_prefix, g_strdup ("env")); + + g_hash_table_iter_init (&iter, opt_unsetenv); + + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + g_ptr_array_add (env_prefix, g_strdup ("-u")); + g_ptr_array_add (env_prefix, g_strdup (key)); + } + + /* If command_and_args[0] contains '=', env(1) can't deal with + * that, so use the standard workaround */ + if (strchr (command_and_args[0], '=') != NULL) + { + g_ptr_array_add (env_prefix, g_strdup ("sh")); + g_ptr_array_add (env_prefix, g_strdup ("-euc")); + g_ptr_array_add (env_prefix, g_strdup ("exec \"$@\"")); + g_ptr_array_add (env_prefix, g_strdup ("sh")); /* argv[0] */ + } + + for (i = 0; command_and_args[i] != NULL; i++) + g_ptr_array_add (env_prefix, g_strdup (command_and_args[i])); + + g_ptr_array_add (env_prefix, NULL); + + command_and_args = (char **) env_prefix->pdata; + } + { g_autoptr(GVariant) fds = NULL; g_autoptr(GVariant) env = NULL; g_autoptr(GVariant) opts = NULL; + g_debug ("Forwarding command:"); + + for (i = 0; command_and_args[i] != NULL; i++) + g_debug ("\t%s", command_and_args[i]); + fds = g_variant_ref_sink (g_variant_builder_end (&fd_builder)); env = g_variant_ref_sink (g_variant_builder_end (&env_builder)); opts = g_variant_ref_sink (g_variant_builder_end (&options_builder)); @@ -725,7 +859,8 @@ out: g_strfreev (forward_fds); g_free (opt_directory); g_free (opt_socket); - g_strfreev (opt_envs); + g_hash_table_unref (opt_env); + g_hash_table_unref (opt_unsetenv); return launch_exit_status; }