[glib] gutils: Add functions for working with environment arrays



commit 409d93148f2d95c2966f75fe0901edd1e06c99a9
Author: Dan Winship <danw gnome org>
Date:   Sat Oct 15 15:52:28 2011 -0400

    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 <literal>g_spawn...</literal> 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
+ * <citerefentry><refentrytitle>signal</refentrytitle><manvolnum>7</manvolnum></citerefentry>)
+ * 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 <literal>g_spawn...</literal> 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.
  *
- * <warning><para>
+ * <warning>
  * 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).
- * </para></warning>
+ *
+ * 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.
+ * </warning>
  *
  * 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.
  *
- * <warning><para>
+ * <warning>
  * 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).
- * </para></warning>
+ *
+ * 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.
+ * </warning>
  *
  * 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:



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]