mirror of
https://github.com/morgan9e/grd
synced 2026-04-14 00:14:18 +09:00
495 lines
14 KiB
C
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");
|
|
}
|