From 409d93148f2d95c2966f75fe0901edd1e06c99a9 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Sat, 15 Oct 2011 15:52:28 -0400 Subject: [PATCH] gutils: Add functions for working with environment arrays When spawning a child process, it is not safe to call setenv() before the fork() (because setenv() isn't thread-safe), but it's also not safe to call it after the fork() (because it's not async-signal-safe). So the only safe way to alter the environment for a child process from a threaded program is to pass a fully-formed envp array to exec*/g_spawn*/etc. So, add g_environ_getenv(), g_environ_setenv(), and g_environ_unsetenv(), which act like their namesakes, but work on arbitrary arrays rather than working directly on the environment. http://bugzilla.gnome.org/show_bug.cgi?id=659326 --- docs/reference/glib/glib-sections.txt | 3 + glib/glib.symbols | 3 + glib/gspawn.h | 36 ++++-- glib/gutils.c | 204 +++++++++++++++++++++++++++++----- glib/gutils.h | 27 +++-- 5 files changed, 225 insertions(+), 48 deletions(-) diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 3015ffa..6edcb8b 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -1665,6 +1665,9 @@ g_set_application_name g_get_prgname g_set_prgname g_get_environ +g_environ_getenv +g_environ_setenv +g_environ_unsetenv g_getenv g_setenv g_unsetenv diff --git a/glib/glib.symbols b/glib/glib.symbols index 95c705d..fc02b14 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -1310,6 +1310,9 @@ g_setenv PRIVATE g_get_host_name g_listenv g_get_environ +g_environ_getenv +g_environ_setenv +g_environ_unsetenv #ifdef G_OS_WIN32 g_find_program_in_path_utf8 g_get_current_dir_utf8 diff --git a/glib/gspawn.h b/glib/gspawn.h index 84e0eeb..ace73d1 100644 --- a/glib/gspawn.h +++ b/glib/gspawn.h @@ -97,24 +97,36 @@ typedef enum * @user_data: user data to pass to the function. * * Specifies the type of the setup function passed to g_spawn_async(), - * g_spawn_sync() and g_spawn_async_with_pipes(). On POSIX platforms it - * is called in the child after GLib has performed all the setup it plans - * to perform but before calling exec(). On POSIX actions taken in this - * function will thus only affect the child, not the parent. + * g_spawn_sync() and g_spawn_async_with_pipes(), which can, in very + * limited ways, be used to affect the child's execution. * - * Note that POSIX allows only async-signal-safe functions (see signal(7)) - * to be called in the child between fork() and exec(), which drastically - * limits the usefulness of child setup functions. + * On POSIX platforms, the function is called in the child after GLib + * has performed all the setup it plans to perform, but before calling + * exec(). Actions taken in this function will only affect the child, + * not the parent. * - * Also note that modifying the environment from the child setup function - * may not have the intended effect, since it will get overridden by - * a non-%NULL @env argument to the g_spawn... functions. - * - * On Windows the function is called in the parent. Its usefulness on + * On Windows, the function is called in the parent. Its usefulness on * Windows is thus questionable. In many cases executing the child setup * function in the parent can have ill effects, and you should be very * careful when porting software to Windows that uses child setup * functions. + * + * However, even on POSIX, you are extremely limited in what you can + * safely do from a #GSpawnChildSetupFunc, because any mutexes that + * were held by other threads in the parent process at the time of the + * fork() will still be locked in the child process, and they will + * never be unlocked (since the threads that held them don't exist in + * the child). POSIX allows only async-signal-safe functions (see + * signal7) + * to be called in the child between fork() and exec(), which + * drastically limits the usefulness of child setup functions. + * + * In particular, it is not safe to call any function which may + * call malloc(), which includes POSIX functions such as setenv(). + * If you need to set up the child environment differently from + * the parent, you should use g_get_environ(), g_environ_setenv(), + * and g_environ_unsetev(), and then pass the complete environment + * list to the g_spawn... function. */ typedef void (* GSpawnChildSetupFunc) (gpointer user_data); diff --git a/glib/gutils.c b/glib/gutils.c index f9ee153..c6ac0b4 100644 --- a/glib/gutils.c +++ b/glib/gutils.c @@ -1215,13 +1215,13 @@ _g_getenv_nomalloc (const gchar *variable, * * Sets an environment variable. Both the variable's name and value * should be in the GLib file name encoding. On UNIX, this means that - * they can be any sequence of bytes. On Windows, they should be in + * they can be arbitrary byte strings. On Windows, they should be in * UTF-8. * - * Note that on some systems, when variables are overwritten, the memory + * Note that on some systems, when variables are overwritten, the memory * used for the previous variables and its value isn't reclaimed. * - * + * * Environment variable handling in UNIX is not thread-safe, and your * program may crash if one thread calls g_setenv() while another * thread is calling getenv(). (And note that many functions, such as @@ -1229,7 +1229,12 @@ _g_getenv_nomalloc (const gchar *variable, * use at the very start of your program, before creating any other * threads (or creating objects that create worker threads of their * own). - * + * + * If you need to set up the environment for a child process, you can + * use g_get_environ() to get an environment array, modify that with + * g_environ_setenv() and g_environ_unsetenv(), and then pass that + * array directly to execvpe(), g_spawn_async(), or the like. + * * * Returns: %FALSE if the environment variable couldn't be set. * @@ -1331,7 +1336,7 @@ extern char **environ; * Note that on some systems, when variables are overwritten, the memory * used for the previous variables and its value isn't reclaimed. * - * + * * Environment variable handling in UNIX is not thread-safe, and your * program may crash if one thread calls g_unsetenv() while another * thread is calling getenv(). (And note that many functions, such as @@ -1339,7 +1344,12 @@ extern char **environ; * use at the very start of your program, before creating any other * threads (or creating objects that create worker threads of their * own). - * + * + * If you need to set up the environment for a child process, you can + * use g_get_environ() to get an environment array, modify that with + * g_environ_setenv() and g_environ_unsetenv(), and then pass that + * array directly to execvpe(), g_spawn_async(), or the like. + * * * Since: 2.4 **/ @@ -1354,31 +1364,13 @@ g_unsetenv (const gchar *variable) unsetenv (variable); #else /* !HAVE_UNSETENV */ - int len; - gchar **e, **f; - g_return_if_fail (variable != NULL); g_return_if_fail (strchr (variable, '=') == NULL); - len = strlen (variable); - /* Mess directly with the environ array. * This seems to be the only portable way to do this. - * - * Note that we remove *all* environment entries for - * the variable name, not just the first. */ - e = f = environ; - while (*e != NULL) - { - if (strncmp (*e, variable, len) != 0 || (*e)[len] != '=') - { - *f = *e; - f++; - } - e++; - } - *f = NULL; + g_environ_unsetenv (environ, variable); #endif /* !HAVE_UNSETENV */ #else /* G_OS_WIN32 */ @@ -1487,7 +1479,7 @@ g_listenv (void) /** * g_get_environ: - * + * * Gets the list of environment variables for the current process. The * list is %NULL terminated and each item in the list is of the form * 'NAME=VALUE'. @@ -1498,7 +1490,8 @@ g_listenv (void) * The return value is freshly allocated and it should be freed with * g_strfreev() when it is no longer needed. * - * Returns: (array zero-terminated=1) (transfer full): the list of environment variables + * Returns: (array zero-terminated=1) (transfer full): the list of + * environment variables * * Since: 2.28 */ @@ -1524,6 +1517,163 @@ g_get_environ (void) #endif } +static gint +g_environ_find (gchar **envp, + const gchar *variable) +{ + gint len, i; + + len = strlen (variable); + + for (i = 0; envp[i]; i++) + { + if (strncmp (envp[i], variable, len) == 0 && + envp[i][len] == '=') + return i; + } + + return -1; +} + +/** + * g_environ_getenv: + * @envp: (array zero-terminated=1) (transfer none): an environment + * list (eg, as returned from g_get_environ()) + * @variable: the environment variable to get, in the GLib file name + * encoding + * + * Returns the value of the environment variable @variable in the + * provided list @envp. + * + * The name and value are in the GLib file name encoding. + * On UNIX, this means the actual bytes which might or might not + * be in some consistent character set and encoding. On Windows, + * it is in UTF-8. On Windows, in case the environment variable's + * value contains references to other environment variables, they + * are expanded. + * + * Return value: the value of the environment variable, or %NULL if + * the environment variable is not set in @envp. The returned + * string is owned by @envp, and will be freed if @variable is + * set or unset again. + * + * Since: 2.32 + */ +const gchar * +g_environ_getenv (gchar **envp, + const gchar *variable) +{ + gint index; + + g_return_val_if_fail (envp != NULL, NULL); + g_return_val_if_fail (variable != NULL, NULL); + + index = g_environ_find (envp, variable); + if (index != -1) + return envp[index] + strlen (variable) + 1; + else + return NULL; +} + +/** + * g_environ_setenv: + * @envp: (array zero-terminated=1) (transfer full): an environment + * list (eg, as returned from g_get_environ()) + * @variable: the environment variable to set, must not contain '=' + * @value: the value for to set the variable to + * @overwrite: whether to change the variable if it already exists + * + * Sets the environment variable @variable in the provided list + * @envp to @value. + * + * Both the variable's name and value should be in the GLib + * file name encoding. On UNIX, this means that they can be + * arbitrary byte strings. On Windows, they should be in UTF-8. + * + * Return value: (array zero-terminated=1) (transfer full): the + * updated environment + * + * Since: 2.32 + */ +gchar ** +g_environ_setenv (gchar **envp, + const gchar *variable, + const gchar *value, + gboolean overwrite) +{ + gint index; + + g_return_val_if_fail (envp != NULL, NULL); + g_return_val_if_fail (variable != NULL, NULL); + g_return_val_if_fail (strchr (variable, '=') == NULL, NULL); + + index = g_environ_find (envp, variable); + if (index != -1) + { + if (overwrite) + { + g_free (envp[index]); + envp[index] = g_strdup_printf ("%s=%s", variable, value); + } + } + else + { + gint length; + + length = g_strv_length (envp); + envp = g_renew (gchar *, envp, length + 2); + envp[length] = g_strdup_printf ("%s=%s", variable, value); + envp[length + 1] = NULL; + } + + return envp; +} + +/** + * g_environ_unsetenv: + * @envp: (array zero-terminated=1) (transfer full): an environment + * list (eg, as returned from g_get_environ()) + * @variable: the environment variable to remove, must not contain '=' + * + * Removes the environment variable @variable from the provided + * environment @envp. + * + * Return value: (array zero-terminated=1) (transfer full): the + * updated environment + * + * Since: 2.32 + */ +gchar ** +g_environ_unsetenv (gchar **envp, + const gchar *variable) +{ + gint len; + gchar **e, **f; + + g_return_val_if_fail (envp != NULL, NULL); + g_return_val_if_fail (variable != NULL, NULL); + g_return_val_if_fail (strchr (variable, '=') == NULL, NULL); + + len = strlen (variable); + + /* Note that we remove *all* environment entries for + * the variable name, not just the first. + */ + e = f = envp; + while (*e != NULL) + { + if (strncmp (*e, variable, len) != 0 || (*e)[len] != '=') + { + *f = *e; + f++; + } + e++; + } + *f = NULL; + + return envp; +} + G_LOCK_DEFINE_STATIC (g_utils_global); static gchar *g_tmp_dir = NULL; diff --git a/glib/gutils.h b/glib/gutils.h index f439934..47d4b16 100644 --- a/glib/gutils.h +++ b/glib/gutils.h @@ -265,17 +265,26 @@ void g_nullify_pointer (gpointer *nullify_location); #endif #endif -const gchar * g_getenv (const gchar *variable); -gboolean g_setenv (const gchar *variable, - const gchar *value, - gboolean overwrite); -void g_unsetenv (const gchar *variable); -gchar** g_listenv (void); -gchar** g_get_environ (void); +const gchar * g_getenv (const gchar *variable); +gboolean g_setenv (const gchar *variable, + const gchar *value, + gboolean overwrite); +void g_unsetenv (const gchar *variable); +gchar ** g_listenv (void); + +gchar ** g_get_environ (void); +const gchar * g_environ_getenv (gchar **envp, + const gchar *variable); +gchar ** g_environ_setenv (gchar **envp, + const gchar *variable, + const gchar *value, + gboolean overwrite) G_GNUC_WARN_UNUSED_RESULT; +gchar ** g_environ_unsetenv (gchar **envp, + const gchar *variable) G_GNUC_WARN_UNUSED_RESULT; /* private */ -const gchar* _g_getenv_nomalloc (const gchar *variable, - gchar buffer[1024]); +const gchar* _g_getenv_nomalloc (const gchar *variable, + gchar buffer[1024]); /** * GVoidFunc: -- 2.7.4