shell-util: Add async-signal-safe wrappers for GLib.spawn_async implementations

In the shell code we often use GLib.spawn_async to launch processes
with a GSpawnChildSetupFunc implementation in JavaScript to reset the
mutter nofile rlimit in the new child process.

However, this is highly unsafe to do because this implies that the child
setup function code is executed in gjs where a lot of allocations are
done and even more not-async-signal-safe code is executed, in fact
leading to dead-locks as reported in the past.

To prevent this, declare a new functions that do the same of the GLib
counterpart but without providing a GSpawnChildSetupFunc that is instead
implemented in the C-side doing the cleanup that mutter requires without
allocations or async-signal-unsafe code.

Helps: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6698
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3293>
This commit is contained in:
Marco Trevisan (Treviño) 2024-05-01 00:27:19 +02:00 committed by Marge Bot
parent dcb5956dea
commit 781010be66
2 changed files with 213 additions and 0 deletions

View File

@ -17,9 +17,11 @@
#include <cogl/cogl.h>
#include "shell-app-cache-private.h"
#include "shell-global.h"
#include "shell-util.h"
#include <glib/gi18n-lib.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <meta/meta-context.h>
#include <meta/display.h>
#include <meta/meta-x11-display.h>
@ -810,3 +812,175 @@ shell_util_get_translated_folder_name (const char *name)
{
return shell_app_cache_translate_folder (shell_app_cache_get_default (), name);
}
static void
spawn_child_setup (gpointer user_data)
{
MetaContext *meta_context = user_data;
/* No async-signal-unsafe code should be called here, so we don't either
* pass the GError to the function, as it may lead to dynamic allocations.
*/
meta_context_restore_rlimit_nofile (meta_context, NULL);
}
/**
* shell_util_spawn_async_with_pipes_and_fds:
* @working_directory: (type filename) (nullable): child's current working
* directory, or %NULL to inherit parent's, in the GLib file name encoding
* @argv: (array zero-terminated=1): child's argument
* vector, in the GLib file name encoding; it must be non-empty and %NULL-terminated
* @envp: (array zero-terminated=1) (nullable):
* child's environment, or %NULL to inherit parent's, in the GLib file
* name encoding
* @flags: flags from #GSpawnFlags
* @stdin_fd: file descriptor to use for child's stdin, or `-1`
* @stdout_fd: file descriptor to use for child's stdout, or `-1`
* @stderr_fd: file descriptor to use for child's stderr, or `-1`
* @source_fds: (array length=n_fds) (nullable): array of FDs from the parent
* process to make available in the child process
* @target_fds: (array length=n_fds) (nullable): array of FDs to remap
* @source_fds to in the child process
* @n_fds: number of FDs in @source_fds and @target_fds
* @stdin_pipe_out: (out) (optional): return location for file descriptor to write to child's stdin, or %NULL
* @stdout_pipe_out: (out) (optional): return location for file descriptor to read child's stdout, or %NULL
* @stderr_pipe_out: (out) (optional): return location for file descriptor to read child's stderr, or %NULL
* @error: return location for error
*
* A wrapper around g_spawn_async_with_pipes_and_fds() with async-signal-safe
* implementation of #GSpawnChildSetupFunc to launch a child program
* asynchronously resetting the rlimit nofile on child setup.
*
* Returns: the PID of the child on success, 0 if error is set
**/
GPid
shell_util_spawn_async_with_pipes_and_fds (const char *working_directory,
const char * const *argv,
const char * const *envp,
GSpawnFlags flags,
int stdin_fd,
int stdout_fd,
int stderr_fd,
const int *source_fds,
const int *target_fds,
size_t n_fds,
int *stdin_pipe_out,
int *stdout_pipe_out,
int *stderr_pipe_out,
GError **error)
{
ShellGlobal *shell_global = shell_global_get ();
GPid child_pid = 0;
if (!g_spawn_async_with_pipes_and_fds (working_directory, argv, envp, flags,
spawn_child_setup, shell_global,
stdin_fd, stdout_fd, stderr_fd,
source_fds, target_fds, n_fds,
&child_pid,
stdin_pipe_out, stdout_pipe_out, stderr_pipe_out,
error))
return 0;
return child_pid;
}
/**
* shell_util_spawn_async_with_pipes:
* @working_directory: (type filename) (nullable): child's current working
* directory, or %NULL to inherit parent's
* @argv: (array zero-terminated=1):
* child's argument vector
* @envp: (array zero-terminated=1) (nullable):
* child's environment, or %NULL to inherit parent's
* @flags: flags from #GSpawnFlags
* @standard_input: (out) (optional): return location for file descriptor to write to child's stdin, or %NULL
* @standard_output: (out) (optional): return location for file descriptor to read child's stdout, or %NULL
* @standard_error: (out) (optional): return location for file descriptor to read child's stderr, or %NULL
* @error: return location for error
*
* A wrapper around g_spawn_async_with_pipes() with async-signal-safe
* implementation of #GSpawnChildSetupFunc to launch a child program
* asynchronously resetting the rlimit nofile on child setup.
*
* Returns: the PID of the child on success, 0 if error is set
**/
GPid
shell_util_spawn_async_with_pipes (const char *working_directory,
const char * const *argv,
const char * const *envp,
GSpawnFlags flags,
int *standard_input,
int *standard_output,
int *standard_error,
GError **error)
{
return shell_util_spawn_async_with_pipes_and_fds (working_directory, argv, envp, flags,
-1, -1, -1, NULL, NULL, 0,
standard_input, standard_output, standard_error,
error);
}
/**
* shell_util_spawn_async_with_fds:
* @working_directory: (type filename) (nullable): child's current working
* directory, or %NULL to inherit parent's
* @argv: (array zero-terminated=1):
* child's argument vector
* @envp: (array zero-terminated=1) (nullable):
* child's environment, or %NULL to inherit parent's
* @flags: flags from #GSpawnFlags
* @stdin_fd: file descriptor to use for child's stdin, or `-1`
* @stdout_fd: file descriptor to use for child's stdout, or `-1`
* @stderr_fd: file descriptor to use for child's stderr, or `-1`
* @error: return location for error
*
* A wrapper around g_spawn_async_with_fds() with async-signal-safe
* implementation of #GSpawnChildSetupFunc to launch a child program
* asynchronously resetting the rlimit nofile on child setup.
*
* Returns: the PID of the child on success, 0 if error is set
**/
GPid
shell_util_spawn_async_with_fds (const char *working_directory,
const char * const *argv,
const char * const *envp,
GSpawnFlags flags,
int stdin_fd,
int stdout_fd,
int stderr_fd,
GError **error)
{
return shell_util_spawn_async_with_pipes_and_fds (working_directory, argv, envp, flags,
stdin_fd, stdout_fd, stderr_fd,
NULL, NULL, 0,
NULL, NULL, NULL,
error);
}
/**
* shell_util_spawn_async:
* @working_directory: (type filename) (nullable): child's current working
* directory, or %NULL to inherit parent's
* @argv: (array zero-terminated=1):
* child's argument vector
* @envp: (array zero-terminated=1) (nullable):
* child's environment, or %NULL to inherit parent's
* @flags: flags from #GSpawnFlags
* @error: return location for error
*
* A wrapper around g_spawn_async() with async-signal-safe implementation of
* #GSpawnChildSetupFunc to launch a child program asynchronously resetting the
* rlimit nofile on child setup.
*
* Returns: the PID of the child on success, 0 if error is set
**/
GPid
shell_util_spawn_async (const char *working_directory,
const char * const *argv,
const char * const *envp,
GSpawnFlags flags,
GError **error)
{
return shell_util_spawn_async_with_pipes (working_directory, argv, envp,
flags, NULL, NULL, NULL, error);
}

View File

@ -80,6 +80,45 @@ char *shell_util_get_translated_folder_name (const char *name);
gint shell_util_get_uid (void);
GPid shell_util_spawn_async_with_pipes_and_fds (const char *working_directory,
const char * const *argv,
const char * const *envp,
GSpawnFlags flags,
int stdin_fd,
int stdout_fd,
int stderr_fd,
const int *source_fds,
const int *target_fds,
size_t n_fds,
int *stdin_pipe_out,
int *stdout_pipe_out,
int *stderr_pipe_out,
GError **error);
GPid shell_util_spawn_async_with_pipes (const char *working_directory,
const char * const *argv,
const char * const *envp,
GSpawnFlags flags,
int *standard_input,
int *standard_output,
int *standard_error,
GError **error);
GPid shell_util_spawn_async_with_fds (const char *working_directory,
const char * const *argv,
const char * const *envp,
GSpawnFlags flags,
int stdin_fd,
int stdout_fd,
int stderr_fd,
GError **error);
GPid shell_util_spawn_async (const char *working_directory,
const char * const *argv,
const char * const *envp,
GSpawnFlags flags,
GError **error);
G_END_DECLS
#endif /* __SHELL_UTIL_H__ */