From de834bed306565c0652050665eafff4dfcdf0d8b Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Sat, 15 Oct 2011 16:59:59 -0400 Subject: [PATCH] GAppLaunchContext: add environment-manipulating functions Add functions for manipulating the environment under which a GAppLaunchContext will launch its children, to avoid thread-related bugs with using setenv() directly. FIXME: win32 side isn't implemented yet https://bugzilla.gnome.org/show_bug.cgi?id=659326 --- docs/reference/gio/gio-sections.txt | 3 + gio/gappinfo.c | 99 +++++++++++++++++++++++- gio/gappinfo.h | 66 ++++++++-------- gio/gdesktopappinfo.c | 145 +++++++++++++++++++++--------------- gio/gio.symbols | 3 + gio/gwin32appinfo.c | 9 +++ 6 files changed, 232 insertions(+), 93 deletions(-) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index 7d7def3..ecfbc1c 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1269,6 +1269,9 @@ g_app_info_get_default_for_uri_scheme g_app_info_get_fallback_for_type g_app_info_get_recommended_for_type g_app_info_launch_default_for_uri +g_app_launch_context_setenv +g_app_launch_context_unsetenv +g_app_launch_context_get_environment g_app_launch_context_get_display g_app_launch_context_get_startup_notify_id g_app_launch_context_launch_failed diff --git a/gio/gappinfo.c b/gio/gappinfo.c index 231a6c5..666d2b0 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -500,10 +500,14 @@ g_app_info_get_icon (GAppInfo *appinfo) * no way to detect this. * * Some URIs can be changed when passed through a GFile (for instance - * unsupported uris with strange formats like mailto:), so if you have - * a textual uri you want to pass in as argument, consider using + * unsupported URIs with strange formats like mailto:), so if you have + * a textual URI you want to pass in as argument, consider using * g_app_info_launch_uris() instead. * + * The launched application inherits the environment of the launching + * process, but it can be modified with g_app_launch_context_setenv() and + * g_app_launch_context_unsetenv(). + * * On UNIX, this function sets the GIO_LAUNCHED_DESKTOP_FILE * environment variable with the path of the launched desktop file and * GIO_LAUNCHED_DESKTOP_FILE_PID to the process @@ -733,6 +737,10 @@ g_app_info_delete (GAppInfo *appinfo) G_DEFINE_TYPE (GAppLaunchContext, g_app_launch_context, G_TYPE_OBJECT); +struct _GAppLaunchContextPrivate { + char **envp; +}; + /** * g_app_launch_context_new: * @@ -748,13 +756,96 @@ g_app_launch_context_new (void) } static void +g_app_launch_context_finalize (GObject *object) +{ + GAppLaunchContext *context = G_APP_LAUNCH_CONTEXT (object); + + g_strfreev (context->priv->envp); + + G_OBJECT_CLASS (g_app_launch_context_parent_class)->finalize (object); +} + +static void g_app_launch_context_class_init (GAppLaunchContextClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (GAppLaunchContextPrivate)); + + object_class->finalize = g_app_launch_context_finalize; } static void -g_app_launch_context_init (GAppLaunchContext *launch_context) +g_app_launch_context_init (GAppLaunchContext *context) +{ + context->priv = G_TYPE_INSTANCE_GET_PRIVATE (context, G_TYPE_APP_LAUNCH_CONTEXT, GAppLaunchContextPrivate); +} + +/** + * g_app_launch_context_setenv: + * @context: a #GAppLaunchContext + * @variable: the environment variable to set + * @value: the value for to set the variable to. + * + * Arranges for @variable to be set to @value in the child's + * environment when @context is used to launch an application. + * + * Since: 2.32 + */ +void +g_app_launch_context_setenv (GAppLaunchContext *context, + const char *variable, + const char *value) +{ + if (!context->priv->envp) + context->priv->envp = g_get_environ (); + + context->priv->envp = + g_environ_setenv (context->priv->envp, variable, value, TRUE); +} + +/** + * g_app_launch_context_unsetenv: + * @context: a #GAppLaunchContext + * @variable: the environment variable to remove + * + * Arranges for @variable to be unset in the child's environment + * when @context is used to launch an application. + * + * Since: 2.32 + */ +void +g_app_launch_context_unsetenv (GAppLaunchContext *context, + const char *variable) { + if (!context->priv->envp) + context->priv->envp = g_get_environ (); + + context->priv->envp = + g_environ_unsetenv (context->priv->envp, variable); +} + +/** + * g_app_launch_context_get_environment: + * @context: a #GAppLaunchContext + * + * Gets the complete environment variable list to be passed to + * the child process when @context is used to launch an application. + * This is a %NULL-terminated array of strings, where each string has + * the form KEY=VALUE. + * + * Return value: (array zero-terminated=1) (transfer full): the + * child's environment + * + * Since: 2.32 + */ +char ** +g_app_launch_context_get_environment (GAppLaunchContext *context) +{ + if (!context->priv->envp) + context->priv->envp = g_get_environ (); + + return g_strdupv (context->priv->envp); } /** @@ -768,7 +859,7 @@ g_app_launch_context_init (GAppLaunchContext *launch_context) * application, by setting the DISPLAY environment variable. * * Returns: a display string for the display. - **/ + */ char * g_app_launch_context_get_display (GAppLaunchContext *context, GAppInfo *info, diff --git a/gio/gappinfo.h b/gio/gappinfo.h index bf44420..b6786d6 100644 --- a/gio/gappinfo.h +++ b/gio/gappinfo.h @@ -33,7 +33,7 @@ G_BEGIN_DECLS #define G_TYPE_APP_INFO (g_app_info_get_type ()) #define G_APP_INFO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_APP_INFO, GAppInfo)) -#define G_IS_APP_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_APP_INFO)) +#define G_IS_APP_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), G_TYPE_APP_INFO)) #define G_APP_INFO_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), G_TYPE_APP_INFO, GAppInfoIface)) #define G_TYPE_APP_LAUNCH_CONTEXT (g_app_launch_context_get_type ()) @@ -136,12 +136,12 @@ struct _GAppInfoIface GType g_app_info_get_type (void) G_GNUC_CONST; GAppInfo * g_app_info_create_from_commandline (const char *commandline, - const char *application_name, - GAppInfoCreateFlags flags, - GError **error); + const char *application_name, + GAppInfoCreateFlags flags, + GError **error); GAppInfo * g_app_info_dup (GAppInfo *appinfo); gboolean g_app_info_equal (GAppInfo *appinfo1, - GAppInfo *appinfo2); + GAppInfo *appinfo2); const char *g_app_info_get_id (GAppInfo *appinfo); const char *g_app_info_get_name (GAppInfo *appinfo); const char *g_app_info_get_display_name (GAppInfo *appinfo); @@ -150,36 +150,36 @@ const char *g_app_info_get_executable (GAppInfo *appin const char *g_app_info_get_commandline (GAppInfo *appinfo); GIcon * g_app_info_get_icon (GAppInfo *appinfo); gboolean g_app_info_launch (GAppInfo *appinfo, - GList *files, - GAppLaunchContext *launch_context, - GError **error); + GList *files, + GAppLaunchContext *launch_context, + GError **error); gboolean g_app_info_supports_uris (GAppInfo *appinfo); gboolean g_app_info_supports_files (GAppInfo *appinfo); gboolean g_app_info_launch_uris (GAppInfo *appinfo, - GList *uris, - GAppLaunchContext *launch_context, - GError **error); + GList *uris, + GAppLaunchContext *launch_context, + GError **error); gboolean g_app_info_should_show (GAppInfo *appinfo); gboolean g_app_info_set_as_default_for_type (GAppInfo *appinfo, - const char *content_type, - GError **error); + const char *content_type, + GError **error); gboolean g_app_info_set_as_default_for_extension (GAppInfo *appinfo, - const char *extension, - GError **error); + const char *extension, + GError **error); gboolean g_app_info_add_supports_type (GAppInfo *appinfo, - const char *content_type, - GError **error); + const char *content_type, + GError **error); gboolean g_app_info_can_remove_supports_type (GAppInfo *appinfo); gboolean g_app_info_remove_supports_type (GAppInfo *appinfo, - const char *content_type, - GError **error); + const char *content_type, + GError **error); gboolean g_app_info_can_delete (GAppInfo *appinfo); gboolean g_app_info_delete (GAppInfo *appinfo); gboolean g_app_info_set_as_last_used_for_type (GAppInfo *appinfo, - const char *content_type, - GError **error); + const char *content_type, + GError **error); GList * g_app_info_get_all (void); GList * g_app_info_get_all_for_type (const char *content_type); @@ -188,12 +188,12 @@ GList * g_app_info_get_fallback_for_type (const gchar *content_type); void g_app_info_reset_type_associations (const char *content_type); GAppInfo *g_app_info_get_default_for_type (const char *content_type, - gboolean must_support_uris); + gboolean must_support_uris); GAppInfo *g_app_info_get_default_for_uri_scheme (const char *uri_scheme); gboolean g_app_info_launch_default_for_uri (const char *uri, - GAppLaunchContext *launch_context, - GError **error); + GAppLaunchContext *launch_context, + GError **error); /** * GAppLaunchContext: @@ -233,14 +233,22 @@ struct _GAppLaunchContextClass GType g_app_launch_context_get_type (void) G_GNUC_CONST; GAppLaunchContext *g_app_launch_context_new (void); + +void g_app_launch_context_setenv (GAppLaunchContext *context, + const char *variable, + const char *value); +void g_app_launch_context_unsetenv (GAppLaunchContext *context, + const char *variable); +char ** g_app_launch_context_get_environment (GAppLaunchContext *context); + char * g_app_launch_context_get_display (GAppLaunchContext *context, - GAppInfo *info, - GList *files); + GAppInfo *info, + GList *files); char * g_app_launch_context_get_startup_notify_id (GAppLaunchContext *context, - GAppInfo *info, - GList *files); + GAppInfo *info, + GList *files); void g_app_launch_context_launch_failed (GAppLaunchContext *context, - const char * startup_notify_id); + const char * startup_notify_id); G_END_DECLS diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 381dbe4..b68d50e 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -1126,9 +1126,8 @@ typedef struct { GSpawnChildSetupFunc user_setup; gpointer user_setup_data; - char *display; - char *sn_id; - char *desktop_file; + + char *pid_envvar; } ChildSetupData; static void @@ -1136,20 +1135,22 @@ child_setup (gpointer user_data) { ChildSetupData *data = user_data; - if (data->display) - g_setenv ("DISPLAY", data->display, TRUE); - - if (data->sn_id) - g_setenv ("DESKTOP_STARTUP_ID", data->sn_id, TRUE); - - if (data->desktop_file) + if (data->pid_envvar) { - gchar pid[20]; - - g_setenv ("GIO_LAUNCHED_DESKTOP_FILE", data->desktop_file, TRUE); + pid_t pid = getpid (); + char buf[20]; + int i; - g_snprintf (pid, 20, "%ld", (long)getpid ()); - g_setenv ("GIO_LAUNCHED_DESKTOP_FILE_PID", pid, TRUE); + /* Write the pid into the space already reserved for it in the + * environment array. We can't use sprintf because it might + * malloc, so we do it by hand. It's simplest to write the pid + * out backwards first, then copy it over. + */ + for (i = 0; pid; i++, pid /= 10) + buf[i] = (pid % 10) + '0'; + for (i--; i >= 0; i--) + *(data->pid_envvar++) = buf[i]; + *data->pid_envvar = '\0'; } if (data->user_setup) @@ -1238,7 +1239,7 @@ _g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, GDBusConnection *session_bus; gboolean completed = FALSE; GList *old_uris; - char **argv; + char **argv, **envp; int argc; ChildSetupData data; @@ -1248,66 +1249,89 @@ _g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + if (launch_context) + envp = g_app_launch_context_get_environment (launch_context); + else + envp = g_get_environ (); + do { GPid pid; GList *launched_uris; GList *iter; + char *display, *sn_id; old_uris = uris; if (!expand_application_parameters (info, &uris, - &argc, &argv, error)) - goto out; + &argc, &argv, error)) + goto out; /* Get the subset of URIs we're launching with this process */ launched_uris = NULL; for (iter = old_uris; iter != NULL && iter != uris; iter = iter->next) - launched_uris = g_list_prepend (launched_uris, iter->data); + launched_uris = g_list_prepend (launched_uris, iter->data); launched_uris = g_list_reverse (launched_uris); - + if (info->terminal && !prepend_terminal_to_vector (&argc, &argv)) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Unable to find terminal required for application")); - goto out; - } + goto out; + } data.user_setup = user_setup; data.user_setup_data = user_setup_data; - data.display = NULL; - data.sn_id = NULL; - data.desktop_file = info->filename; + if (info->filename) + { + envp = g_environ_setenv (envp, + "GIO_LAUNCHED_DESKTOP_FILE", + info->filename, + TRUE); + envp = g_environ_setenv (envp, + "GIO_LAUNCHED_DESKTOP_FILE_PID", + "XXXXXXXXXXXXXXXXXXXX", /* filled in child_setup */ + TRUE); + data.pid_envvar = (char *)g_environ_getenv (envp, "GIO_LAUNCHED_DESKTOP_FILE_PID"); + } + + display = NULL; + sn_id = NULL; if (launch_context) - { - GList *launched_files = create_files_for_uris (launched_uris); - - data.display = g_app_launch_context_get_display (launch_context, - appinfo, - launched_files); - - if (info->startup_notify) - data.sn_id = g_app_launch_context_get_startup_notify_id (launch_context, - appinfo, - launched_files); - g_list_foreach (launched_files, (GFunc)g_object_unref, NULL); - g_list_free (launched_files); - } + { + GList *launched_files = create_files_for_uris (launched_uris); + + display = g_app_launch_context_get_display (launch_context, + appinfo, + launched_files); + envp = g_environ_setenv (envp, "DISPLAY", display, TRUE); + + if (info->startup_notify) + { + sn_id = g_app_launch_context_get_startup_notify_id (launch_context, + appinfo, + launched_files); + envp = g_environ_setenv (envp, "DESKTOP_STARTUP_ID", sn_id, TRUE); + } + + g_list_foreach (launched_files, (GFunc)g_object_unref, NULL); + g_list_free (launched_files); + } if (!g_spawn_async (info->path, - argv, - NULL, - spawn_flags, - child_setup, - &data, - &pid, - error)) - { - if (data.sn_id) - g_app_launch_context_launch_failed (launch_context, data.sn_id); + argv, + envp, + spawn_flags, + child_setup, + &data, + &pid, + error)) + { + if (sn_id) + g_app_launch_context_launch_failed (launch_context, sn_id); - g_free (data.sn_id); - g_free (data.display); + g_free (display); + g_free (sn_id); g_list_free (launched_uris); goto out; @@ -1319,12 +1343,12 @@ _g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, notify_desktop_launch (session_bus, info, pid, - data.display, - data.sn_id, + display, + sn_id, launched_uris); - g_free (data.sn_id); - g_free (data.display); + g_free (display); + g_free (sn_id); g_list_free (launched_uris); g_strfreev (argv); @@ -1349,6 +1373,7 @@ _g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, out: g_strfreev (argv); + g_strfreev (envp); return completed; } @@ -1438,9 +1463,9 @@ g_desktop_app_info_launch (GAppInfo *appinfo, * * This guarantee allows additional control over the exact environment * of the child processes, which is provided via a setup function - * @setup, as well as the process identifier of each child process via - * @pid_callback. See g_spawn_async() for more information about the - * semantics of the @setup function. + * @user_setup, as well as the process identifier of each child process + * via @pid_callback. See g_spawn_async() for more information about the + * semantics of the @user_setup function. * * Returns: %TRUE on successful launch, %FALSE otherwise. */ diff --git a/gio/gio.symbols b/gio/gio.symbols index 029c102..0ce8fd5 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -66,6 +66,9 @@ g_app_info_launch_default_for_uri g_app_info_can_delete g_app_info_delete g_app_launch_context_new +g_app_launch_context_setenv +g_app_launch_context_unsetenv +g_app_launch_context_get_environment g_app_launch_context_get_display g_app_launch_context_get_startup_notify_id g_app_launch_context_launch_failed diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index a441251..cb0093f 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -278,6 +278,15 @@ g_win32_app_info_launch (GAppInfo *appinfo, } #endif + /* FIXME: Need to do something with + * g_app_launch_context_get_environment()... ShellExecuteExW() + * doesn't have any way to pass an environment though. We need to + * either (a) update environment, ShellExecuteExW(), revert + * environment; or (b) find an API to figure out what app + * ShellExecuteExW() would launch, and then use g_spawn_async() + * instead. + */ + for (l = files; l != NULL; l = l->next) { char *path = g_file_get_path (l->data); -- 2.7.4