Skip to content
Snippets Groups Projects
Commit 38633885 authored by Simon McVittie's avatar Simon McVittie
Browse files

launch: Add support for forwarding and unsetting environment variables


To avoid needing special support for this on the launcher side, we
implement this by wrapping the command in `env -u VAR`.

Signed-off-by: default avatarSimon McVittie <smcv@collabora.com>
parent 9bb93f6f
No related branches found
No related tags found
No related merge requests found
......@@ -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&#x2a;" --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&#x2a;** 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&#x2a;"**.
**--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.
......
......@@ -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;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment