Merge branch 'rebase_unixexec' into 'main'

gio: implement unixexec dbus server address support

Closes #1275

See merge request GNOME/glib!3705
This commit is contained in:
Ómar Högni Guðmarsson 2024-07-15 17:52:51 +00:00
commit 172fd8445d
2 changed files with 302 additions and 3 deletions

View File

@ -43,10 +43,19 @@
#include "gstdio.h"
#ifdef HAVE_UNISTD_H
#include <fcntl.h>
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#ifdef G_OS_UNIX
#include <sys/socket.h>
#endif
#ifdef __linux__
#include <sys/prctl.h>
#endif
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <gio/gunixsocketaddress.h>
#ifdef G_OS_WIN32
@ -74,6 +83,8 @@
*
* Since GLib 2.72, `unix:` addresses are supported on Windows with `AF_UNIX`
* support (Windows 10).
*
* Since GLib 2.80, `unixexec:` addresses are supported on Unix systems.
*/
static gchar *get_session_address_platform_specific (GError **error);
@ -288,6 +299,28 @@ is_valid_nonce_tcp (const gchar *address_entry,
return ret;
}
#ifdef G_OS_UNIX
static gboolean
is_valid_unixexec (const gchar *address_entry,
GHashTable *key_value_pairs,
GError **error)
{
const gchar *path = g_hash_table_lookup (key_value_pairs, "path");
if (path == NULL ||
path[0] == '\0')
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT,
_("Error in address “%s” — missing key “path”"),
address_entry);
return FALSE;
}
return TRUE;
}
#endif
static gboolean
is_valid_tcp (const gchar *address_entry,
GHashTable *key_value_pairs,
@ -413,6 +446,10 @@ g_dbus_is_supported_address (const gchar *string,
supported = is_valid_tcp (a[n], key_value_pairs, error);
else if (g_strcmp0 (transport_name, "nonce-tcp") == 0)
supported = is_valid_nonce_tcp (a[n], key_value_pairs, error);
#ifdef G_OS_UNIX
else if (g_strcmp0 (transport_name, "unixexec") == 0)
supported = is_valid_unixexec (a[n], key_value_pairs, error);
#endif
else if (g_strcmp0 (a[n], "autolaunch:") == 0)
supported = TRUE;
else
@ -545,7 +582,44 @@ out:
return ret;
}
#ifdef G_OS_UNIX
/* This function is called between fork() and exec() and hence must be
* async-signal-safe (see signal-safety(7)). */
static gint
safe_dup2 (gint fd1, gint fd2)
{
gint ret;
do
ret = dup2 (fd1, fd2);
while (ret < 0 && (errno == EINTR || errno == EBUSY));
return ret;
}
/* ---------------------------------------------------------------------------------------------------- */
static void
unixexec_prepare_child (gpointer data)
{
int fd;
gint *s = (gint *) data;
/* We want to keep socket pair alive. Therefore we don't set CLOEXEC on it */
fd = safe_dup2 (s[1], STDIN_FILENO);
g_assert (fd == STDIN_FILENO);
fd = safe_dup2 (s[1], STDOUT_FILENO);
g_assert (fd == STDOUT_FILENO);
}
static void
unixexec_child_watch (GPid pid,
gint status,
gpointer user_data)
{
g_io_stream_close (G_IO_STREAM (user_data), NULL, NULL);
g_spawn_close_pid (pid);
}
#endif
static GIOStream *
g_dbus_address_try_connect_one (const gchar *address_entry,
@ -553,6 +627,58 @@ g_dbus_address_try_connect_one (const gchar *address_entry,
GCancellable *cancellable,
GError **error);
#ifdef G_OS_UNIX
/* Mark it as static and keep it in gdbusaddress.c until some other glib
file needs it, returns TRUE on SUCCESS and FALSE on error.
*/
static gboolean
g_socketpair (gint domain,
gint type,
gint protocol,
gint *s,
GError **error)
{
int r;
int errsv;
#ifdef SOCK_CLOEXEC
r = socketpair (domain, type | SOCK_CLOEXEC, protocol, s);
errsv = errno;
/* It's possible that libc has SOCK_CLOEXEC but the kernel does not */
if (r == -1 && (errsv == EINVAL || errsv == EPROTOTYPE))
#endif
r = socketpair (domain, type, protocol, s);
if (r == -1)
{
int errsv = errno;
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
_("Unable to create socketpair: %s"), g_strerror (errsv));
errno = errsv;
return FALSE;
}
if (fcntl (s[0], F_SETFD, FD_CLOEXEC) < 0 ||
fcntl (s[1], F_SETFD, FD_CLOEXEC) < 0)
{
int errsv = errno;
g_close (s[0], NULL);
g_close (s[1], NULL);
s[0] = -1;
s[1] = -1;
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (errsv),
_("Failed to call fcntl: %s"),
g_strerror (errsv));
return FALSE;
}
return TRUE;
}
#endif
/* TODO: Declare an extension point called GDBusTransport (or similar)
* and move code below to extensions implementing said extension
* point. That way we can implement a D-Bus transport over X11 without
@ -603,6 +729,92 @@ g_dbus_address_connect (const gchar *address_entry,
g_assert_not_reached ();
}
}
#ifdef G_OS_UNIX
else if (g_strcmp0 (transport_name, "unixexec") == 0)
{
gint s[2];
GPid child_pid;
GSocket *fdsocket;
const gchar **argv;
guint i = 0;
gchar *args;
guint argnum = 1;
if (!g_socketpair (AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, s, error))
goto out;
argv = g_malloc_n (g_hash_table_size (key_value_pairs) + 2, sizeof (gchar *)); // Can return NULL if allocation fails
if (argv == NULL)
{
g_close (s[0], NULL);
g_close (s[1], NULL);
s[0] = -1;
s[1] = -1;
goto out;
}
/* We know path exist if we made it here */
argv[i++] = g_hash_table_lookup (key_value_pairs, "path");
if (g_hash_table_contains (key_value_pairs, "argv0"))
argv[i++] = g_hash_table_lookup (key_value_pairs, "argv0");
else
argv[i++] = g_hash_table_lookup (key_value_pairs, "path");
for (args = g_strdup_printf ("argv%u", argnum++); g_hash_table_contains (key_value_pairs, args);)
{
argv[i++] = g_hash_table_lookup (key_value_pairs, args);
g_free (args);
args = g_strdup_printf ("argv%u", argnum++);
}
g_free (args);
argv[i++] = NULL;
if (!g_spawn_async_with_pipes (NULL,
(gchar **) argv,
NULL,
G_SPAWN_SEARCH_PATH |
G_SPAWN_CHILD_INHERITS_STDIN |
G_SPAWN_DO_NOT_REAP_CHILD |
G_SPAWN_FILE_AND_ARGV_ZERO,
unixexec_prepare_child,
s,
&child_pid,
NULL,
NULL,
NULL,
error))
{
g_close (s[0], NULL);
g_close (s[1], NULL);
s[0] = -1;
s[1] = -1;
g_free (argv);
goto out;
}
g_close (s[1], NULL);
g_free (argv);
fdsocket = g_socket_new_from_fd (s[0], error);
if (fdsocket == NULL)
{
g_close (s[0], NULL);
g_spawn_close_pid (child_pid);
goto out;
}
GMainContext *worker_context;
GSource *source;
worker_context = GLIB_PRIVATE_CALL (g_get_worker_context) ();
ret = (GIOStream *) g_socket_connection_factory_create_connection (fdsocket);
g_object_unref(fdsocket);
source = g_child_watch_source_new (child_pid);
g_source_set_callback (source, (GSourceFunc) unixexec_child_watch, ret, NULL);
g_source_attach (source, worker_context);
g_source_unref (source);
}
#endif
else if (g_strcmp0 (transport_name, "tcp") == 0 || g_strcmp0 (transport_name, "nonce-tcp") == 0)
{
const gchar *s;

View File

@ -106,9 +106,7 @@ test_address_parsing (void)
static void
test_unix_address (void)
{
#ifndef G_OS_UNIX
g_test_skip ("unix transport is not supported on non-Unix platforms");
#else
#ifdef G_OS_UNIX
assert_is_supported_address ("unix:path=/tmp/dbus-test");
assert_is_supported_address ("unix:path=/tmp/dbus-test,guid=0");
assert_is_supported_address ("unix:abstract=/tmp/dbus-another-test");
@ -125,6 +123,93 @@ test_unix_address (void)
assert_not_supported_address ("unix:path=/tmp,dir=/tmp");
assert_not_supported_address ("unix:abstract=/tmp/foo,dir=/tmp");
assert_not_supported_address ("unix:");
#else
g_test_skip ("Skipping Unix address tests on non-Unix platform");
#endif
}
static void
test_unixexec_address_connect (void)
{
#ifdef G_OS_UNIX
/* If the host machine has socat program, test program uses
* socat to connect to the session bus to retrieve dbus client
* list */
GError *error = NULL;
GDBusConnection *con;
gchar *addr;
gchar *unixexec;
if (g_find_program_in_path ("socat") == NULL)
{
g_test_skip ("Skipping Unix address tests because socat is not available");
return;
}
addr = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (addr);
if (!g_str_has_prefix (addr, "unix:path="))
{
g_free (addr);
g_test_skip ("Skipping Unix address tests because address prefix is not unix:path=");
return;
}
unixexec = g_strdup_printf ("unixexec:path=socat,argv1=STDIO,argv2=%s", addr + strlen ("unix:path="));
g_free (addr);
con = g_dbus_connection_new_for_address_sync (
unixexec,
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
NULL,
NULL,
&error);
g_assert_no_error (error);
g_free (unixexec);
g_dbus_connection_call_sync (
con,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"ListNames",
NULL,
G_VARIANT_TYPE ("(as)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert_no_error (error);
g_free (con);
#else
g_test_skip ("Skipping Unix address tests on non-Unix platform");
#endif
}
static void
test_unixexec_address (void)
{
#ifdef G_OS_UNIX
GError *error = NULL;
GIOStream *s;
assert_is_supported_address ("unixexec:path=/abc,argv0=0,argv1=1,argv2=2");
assert_is_supported_address ("unixexec:path=/abc");
assert_is_supported_address ("unixexec:path=/abc,argv0=0");
assert_is_supported_address ("unixexec:path=./abc,argv0=0");
assert_not_supported_address ("unixexec:argv0=0,argv1=1,argv2=2");
g_assert_false (g_dbus_is_address ("unixexecpath=/abc"));
assert_not_supported_address ("unixexec:argv0=0,argv1=1,argv2=2");
s = g_dbus_address_get_stream_sync ("unixexec:path=cat",
NULL,
NULL,
&error);
g_assert_no_error (error);
g_io_stream_close(s, NULL, NULL);
#else
g_test_skip ("Skipping Unix address tests on non-Unix platform");
#endif
}
@ -215,6 +300,8 @@ main (int argc,
g_test_add_func ("/gdbus/unsupported-address", test_unsupported_address);
g_test_add_func ("/gdbus/address-parsing", test_address_parsing);
g_test_add_func ("/gdbus/unix-address", test_unix_address);
g_test_add_func ("/gdbus/unixexec-address-connect", test_unixexec_address_connect);
g_test_add_func ("/gdbus/unixexec-address", test_unixexec_address);
g_test_add_func ("/gdbus/nonce-tcp-address", test_nonce_tcp_address);
g_test_add_func ("/gdbus/tcp-address", test_tcp_address);
g_test_add_func ("/gdbus/autolaunch-address", test_autolaunch_address);