Files
grd/grd-utils.c
2026-02-13 13:06:50 +09:00

495 lines
14 KiB
C

/*
* Copyright (C) 2010 Intel Corp.
* Copyright (C) 2014 Jonas Ådahl
* Copyright (C) 2016-2022 Red Hat Inc.
* Copyright (C) 2022 Pascal Nowack
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "config.h"
#include "grd-utils.h"
#include <gio/gunixfdlist.h>
#include <gio/gunixinputstream.h>
#include <glib/gstdio.h>
#define GRD_SERVER_PORT_RANGE 10
typedef struct _GrdFdSource
{
GSource source;
GSourceFunc prepare;
GSourceFunc dispatch;
gpointer user_data;
GPollFD poll_fd;
} GrdFdSource;
void
grd_sync_point_init (GrdSyncPoint *sync_point)
{
g_cond_init (&sync_point->sync_cond);
g_mutex_init (&sync_point->sync_mutex);
}
void
grd_sync_point_clear (GrdSyncPoint *sync_point)
{
g_cond_clear (&sync_point->sync_cond);
g_mutex_clear (&sync_point->sync_mutex);
}
void
grd_sync_point_reset (GrdSyncPoint *sync_point)
{
sync_point->completed = FALSE;
sync_point->success = FALSE;
}
void
grd_sync_point_complete (GrdSyncPoint *sync_point,
gboolean success)
{
g_return_if_fail (!sync_point->completed);
g_mutex_lock (&sync_point->sync_mutex);
sync_point->success = success;
sync_point->completed = TRUE;
g_cond_signal (&sync_point->sync_cond);
g_mutex_unlock (&sync_point->sync_mutex);
}
gboolean
grd_sync_point_wait_for_completion (GrdSyncPoint *sync_point)
{
gboolean success;
g_mutex_lock (&sync_point->sync_mutex);
while (!sync_point->completed)
g_cond_wait (&sync_point->sync_cond, &sync_point->sync_mutex);
success = sync_point->success;
g_mutex_unlock (&sync_point->sync_mutex);
return success;
}
static gboolean
grd_fd_source_prepare (GSource *source,
int *timeout_ms)
{
GrdFdSource *fd_source = (GrdFdSource *) source;
*timeout_ms = -1;
return fd_source->prepare (fd_source->user_data);
}
static gboolean
grd_fd_source_check (GSource *source)
{
GrdFdSource *fd_source = (GrdFdSource *) source;
return !!(fd_source->poll_fd.revents & G_IO_IN);
}
static gboolean
grd_fd_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
GrdFdSource *fd_source = (GrdFdSource *) source;
return fd_source->dispatch (fd_source->user_data);
}
static void
grd_fd_source_finalize (GSource *source)
{
GrdFdSource *fd_source = (GrdFdSource *) source;
close (fd_source->poll_fd.fd);
}
static GSourceFuncs fd_source_funcs =
{
.prepare = grd_fd_source_prepare,
.check = grd_fd_source_check,
.dispatch = grd_fd_source_dispatch,
.finalize = grd_fd_source_finalize,
};
GSource *
grd_create_fd_source (int fd,
const char *name,
GSourceFunc prepare,
GSourceFunc dispatch,
gpointer user_data,
GDestroyNotify notify)
{
GSource *source;
GrdFdSource *fd_source;
source = g_source_new (&fd_source_funcs, sizeof (GrdFdSource));
g_source_set_name (source, name);
fd_source = (GrdFdSource *) source;
fd_source->poll_fd.fd = fd;
fd_source->poll_fd.events = G_IO_IN;
fd_source->prepare = prepare;
fd_source->dispatch = dispatch;
fd_source->user_data = user_data;
g_source_set_callback (source, dispatch, user_data, notify);
g_source_add_poll (source, &fd_source->poll_fd);
return source;
}
gboolean
grd_bind_socket (GSocketListener *server,
uint16_t port,
uint16_t *selected_port,
gboolean negotiate_port,
GError **error)
{
gboolean is_bound = FALSE;
int i;
if (!negotiate_port)
{
is_bound = g_socket_listener_add_inet_port (server,
port,
NULL,
error);
goto out;
}
for (i = 0; i < GRD_SERVER_PORT_RANGE; i++, port++)
{
g_autoptr (GError) local_error = NULL;
g_assert (port < G_MAXUINT16);
is_bound = g_socket_listener_add_inet_port (server,
port,
NULL,
&local_error);
if (local_error)
{
g_debug ("\tServer could not be bound to TCP port %hu: %s",
port, local_error->message);
}
if (is_bound)
break;
}
if (!is_bound)
port = g_socket_listener_add_any_inet_port (server, NULL, error);
is_bound = port != 0;
out:
if (is_bound)
{
g_debug ("\tServer bound to TCP port %hu", port);
*selected_port = port;
}
return is_bound;
}
void
grd_rewrite_path_to_user_data_dir (char **path,
const char *subdir,
const char *fallback_path)
{
const char *input_path = NULL;
g_autofree char *basename = NULL;
g_autofree char *output_path = NULL;
g_assert (path);
g_assert (subdir);
g_assert (fallback_path);
g_assert (strstr (subdir, "..") == NULL);
input_path = *path;
if (!input_path ||
input_path[0] == '\0' ||
g_str_equal (input_path, ".") ||
G_IS_DIR_SEPARATOR (input_path[strlen (input_path) - 1]))
input_path = fallback_path;
basename = g_path_get_basename (input_path);
g_assert (!G_IS_DIR_SEPARATOR (basename[0]));
output_path = g_build_filename (g_get_user_data_dir (),
"gnome-remote-desktop",
subdir,
basename,
NULL);
g_free (*path);
*path = g_steal_pointer (&output_path);
}
gboolean
grd_write_fd_to_file (int fd,
const char *filename,
GCancellable *cancellable,
GError **error)
{
g_autoptr (GInputStream) input_stream = NULL;
g_autoptr (GFileOutputStream) output_stream = NULL;
g_autoptr (GFile) file = NULL;
g_autofree char *dir = g_path_get_dirname (filename);
g_mkdir_with_parents (dir, 0755);
input_stream = G_INPUT_STREAM (g_unix_input_stream_new (fd, FALSE));
file = g_file_new_for_path (filename);
output_stream = g_file_replace (file, NULL, TRUE, G_FILE_CREATE_PRIVATE,
NULL, error);
if (!output_stream)
return FALSE;
return g_output_stream_splice (G_OUTPUT_STREAM(output_stream), input_stream,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
cancellable,
error);
}
gboolean
grd_test_fd (int fd,
ssize_t max_size,
GFileTest *test_results,
GError **error)
{
struct stat stat_results;
int ret;
do
ret = fstat (fd, &stat_results);
while (ret < 0 && errno == EINTR);
if (ret < 0)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
"Failed to fstat file descriptor: %s", g_strerror (errno));
return FALSE;
}
if (max_size >= 0 && S_ISREG (stat_results.st_mode) &&
stat_results.st_size > max_size)
{
g_autofree char *size_string = NULL;
size_string = g_format_size_full (max_size, G_FORMAT_SIZE_LONG_FORMAT);
g_set_error (error, G_IO_ERROR, G_FILE_ERROR_FAILED,
"File size exceeds %s", size_string);
return FALSE;
}
*test_results = 0;
if (S_ISREG (stat_results.st_mode))
*test_results |= G_FILE_TEST_IS_REGULAR;
else if (S_ISDIR (stat_results.st_mode))
*test_results |= G_FILE_TEST_IS_DIR;
else if (S_ISLNK (stat_results.st_mode))
*test_results |= G_FILE_TEST_IS_SYMLINK;
if (stat_results.st_nlink > 0)
*test_results |= G_FILE_TEST_EXISTS;
if ((stat_results.st_mode & S_IXUSR) || (stat_results.st_mode & S_IXGRP) ||
(stat_results.st_mode & S_IXOTH))
*test_results |= G_FILE_TEST_IS_EXECUTABLE;
return TRUE;
}
gboolean
grd_toggle_systemd_unit (GrdRuntimeMode runtime_mode,
gboolean enabled,
GError **error)
{
g_autoptr (GStrvBuilder) builder = NULL;
g_autofree char *error_output = NULL;
g_auto (GStrv) new_argv = NULL;
g_autofree char *pid = NULL;
const char *type;
int wait_status;
gboolean success;
switch (runtime_mode)
{
case GRD_RUNTIME_MODE_HEADLESS:
type = "headless";
break;
case GRD_RUNTIME_MODE_SYSTEM:
type = "system";
break;
case GRD_RUNTIME_MODE_SCREEN_SHARE:
type = "user";
break;
default:
g_assert_not_reached ();
}
builder = g_strv_builder_new ();
if (runtime_mode == GRD_RUNTIME_MODE_SYSTEM)
g_strv_builder_add (builder, "pkexec");
g_strv_builder_add (builder,
GRD_LIBEXEC_DIR "/gnome-remote-desktop-enable-service");
pid = g_strdup_printf ("%d", getpid ());
g_strv_builder_add (builder, pid);
g_strv_builder_add (builder, type);
g_strv_builder_add (builder, enabled ? "true" : "false");
new_argv = g_strv_builder_end (builder);
success = g_spawn_sync (NULL,
new_argv,
NULL,
G_SPAWN_SEARCH_PATH |
G_SPAWN_CHILD_INHERITS_STDOUT,
NULL,
NULL,
NULL,
&error_output,
&wait_status,
error);
if (!success)
return FALSE;
if (!WIFEXITED (wait_status) || WEXITSTATUS (wait_status) != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Could not %s %s service:\n%s",
enabled? "enable" : "disable",
type, error_output);
return FALSE;
}
return success;
}
gboolean
grd_systemd_get_unit (GBusType bus_type,
const char *unit,
GDBusProxy **proxy,
GError **error)
{
g_autoptr (GDBusProxy) manager_proxy = NULL;
g_autoptr (GDBusProxy) unit_proxy = NULL;
g_autoptr (GVariant) result = NULL;
g_autofree char *object_path = NULL;
manager_proxy = g_dbus_proxy_new_for_bus_sync (
bus_type,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
NULL,
error);
if (!manager_proxy)
return FALSE;
result = g_dbus_proxy_call_sync (manager_proxy,
"LoadUnit",
g_variant_new ("(s)", unit),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
error);
if (!result)
return FALSE;
g_variant_get (result, "(o)", &object_path);
unit_proxy = g_dbus_proxy_new_for_bus_sync (bus_type,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.freedesktop.systemd1",
object_path,
"org.freedesktop.systemd1.Unit",
NULL,
error);
if (!unit_proxy)
return FALSE;
*proxy = g_steal_pointer (&unit_proxy);
return TRUE;
}
gboolean
grd_systemd_unit_get_active_state (GDBusProxy *unit_proxy,
GrdSystemdUnitActiveState *active_state,
GError **error)
{
g_autoptr (GVariant) result = NULL;
g_autofree char *res_active_state = NULL;
result = g_dbus_proxy_get_cached_property (unit_proxy, "ActiveState");
if (!result)
{
g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY,
"An error occurred while getting the ActiveState property");
return FALSE;
}
g_variant_get (result, "s", &res_active_state);
if (g_str_equal (res_active_state, "active"))
*active_state = GRD_SYSTEMD_UNIT_ACTIVE_STATE_ACTIVE;
else if (g_str_equal (res_active_state, "reloading"))
*active_state = GRD_SYSTEMD_UNIT_ACTIVE_STATE_RELOADING;
else if (g_str_equal (res_active_state, "inactive"))
*active_state = GRD_SYSTEMD_UNIT_ACTIVE_STATE_INACTIVE;
else if (g_str_equal (res_active_state, "failed"))
*active_state = GRD_SYSTEMD_UNIT_ACTIVE_STATE_FAILED;
else if (g_str_equal (res_active_state, "activating"))
*active_state = GRD_SYSTEMD_UNIT_ACTIVE_STATE_ACTIVATING;
else if (g_str_equal (res_active_state, "deactivating"))
*active_state = GRD_SYSTEMD_UNIT_ACTIVE_STATE_DEACTIVATING;
else
*active_state = GRD_SYSTEMD_UNIT_ACTIVE_STATE_UNKNOWN;
return TRUE;
}
void
grd_close_connection_and_notify (GSocketConnection *connection)
{
g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
g_object_notify (G_OBJECT (connection), "closed");
}