1
0
Fork 0
gnome-control-center/panels/applications/cc-snapd-client.c
Daniel Baumann 0a49575b51
Adding upstream version 1:48.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 19:52:18 +02:00

290 lines
9.5 KiB
C

/* cc-snapd-client.c
*
* Copyright 2023 Canonical Ltd.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <libsoup/soup.h>
#include "cc-snapd-client.h"
// Unix socket that snapd communicates on.
#define SNAPD_SOCKET_PATH "/var/run/snapd.socket"
struct _CcSnapdClient
{
GObject parent;
// HTTP connection to snapd.
SoupSession *session;
};
G_DEFINE_TYPE (CcSnapdClient, cc_snapd_client, G_TYPE_OBJECT)
// Make an HTTP request to send to snapd.
static SoupMessage *
make_message (const gchar *method, const gchar *path, JsonNode *request_body)
{
g_autofree gchar *uri = NULL;
SoupMessage *msg;
SoupMessageHeaders *request_headers;
uri = g_strdup_printf("http://localhost%s", path);
msg = soup_message_new (method, uri);
request_headers = soup_message_get_request_headers (msg);
// Allow authentication via polkit.
soup_message_headers_append (request_headers, "X-Allow-Interaction", "true");
if (request_body != NULL)
{
g_autoptr(JsonGenerator) generator = NULL;
g_autofree gchar *body_text = NULL;
gsize body_length;
g_autoptr(GBytes) body_bytes = NULL;
generator = json_generator_new ();
json_generator_set_root (generator, request_body);
body_text = json_generator_to_data (generator, &body_length);
body_bytes = g_bytes_new (body_text, body_length);
soup_message_set_request_body_from_bytes (msg, "application/json", body_bytes);
}
return msg;
}
// Process an HTTP response recveived from snapd.
static JsonObject *
process_body (SoupMessage *msg, GBytes *body, GError **error)
{
const gchar *content_type;
g_autoptr(JsonParser) parser = NULL;
const gchar *body_data;
size_t body_length;
JsonNode *root;
JsonObject *response;
gint64 status_code;
g_autoptr(GError) internal_error = NULL;
content_type = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Content-Type");
if (g_strcmp0 (content_type, "application/json") != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid content type %s returned", content_type);
return NULL;
}
parser = json_parser_new ();
body_data = g_bytes_get_data (body, &body_length);
if (!json_parser_load_from_data (parser, body_data, body_length, &internal_error))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to decode JSON content: %s", internal_error->message);
return NULL;
}
root = json_parser_get_root (parser);
if (!JSON_NODE_HOLDS_OBJECT (root))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Returned JSON not an object");
return NULL;
}
response = json_node_get_object (root);
status_code = json_object_get_int_member (response, "status-code");
if (status_code != SOUP_STATUS_OK && status_code != SOUP_STATUS_ACCEPTED)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid status code %" G_GINT64_FORMAT, status_code);
return NULL;
}
return json_object_ref (response);
}
// Send an HTTP request to snapd and process the response.
static JsonObject *
call_sync (CcSnapdClient *self,
const gchar *method, const gchar *path, JsonNode *request_body,
GCancellable *cancellable, GError **error)
{
g_autoptr(SoupMessage) msg = NULL;
g_autoptr(GBytes) response_body = NULL;
msg = make_message (method, path, request_body);
response_body = soup_session_send_and_read (self->session, msg, cancellable, error);
if (response_body == NULL)
return NULL;
return process_body (msg, response_body, error);
}
// Perform a snap interface action.
static gchar *
call_interfaces_sync (CcSnapdClient *self,
const gchar *action,
const gchar *plug_snap, const gchar *plug_name,
const gchar *slot_snap, const gchar *slot_name,
GCancellable *cancellable, GError **error)
{
g_autoptr(JsonBuilder) builder = NULL;
g_autoptr(JsonObject) response = NULL;
const gchar *change_id;
builder = json_builder_new();
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "action");
json_builder_add_string_value (builder, action);
json_builder_set_member_name (builder, "plugs");
json_builder_begin_array (builder);
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "snap");
json_builder_add_string_value (builder, plug_snap);
json_builder_set_member_name (builder, "plug");
json_builder_add_string_value (builder, plug_name);
json_builder_end_object (builder);
json_builder_end_array (builder);
json_builder_set_member_name (builder, "slots");
json_builder_begin_array (builder);
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "snap");
json_builder_add_string_value (builder, slot_snap);
json_builder_set_member_name (builder, "slot");
json_builder_add_string_value (builder, slot_name);
json_builder_end_object (builder);
json_builder_end_array (builder);
json_builder_end_object (builder);
response = call_sync (self, "POST", "/v2/interfaces",
json_builder_get_root (builder), cancellable, error);
if (response == NULL)
return NULL;
change_id = json_object_get_string_member (response, "change");
return g_strdup (change_id);
}
static void
cc_snapd_client_dispose (GObject *object)
{
CcSnapdClient *self = CC_SNAPD_CLIENT (object);
g_clear_object(&self->session);
G_OBJECT_CLASS (cc_snapd_client_parent_class)->dispose (object);
}
static void
cc_snapd_client_class_init (CcSnapdClientClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = cc_snapd_client_dispose;
}
static void
cc_snapd_client_init (CcSnapdClient *self)
{
g_autoptr(GSocketAddress) address = g_unix_socket_address_new (SNAPD_SOCKET_PATH);
self->session = soup_session_new_with_options ("remote-connectable", address, NULL);
}
CcSnapdClient *
cc_snapd_client_new (void)
{
return g_object_new (CC_TYPE_SNAPD_CLIENT, NULL);
}
JsonObject *
cc_snapd_client_get_snap_sync (CcSnapdClient *self, const gchar *name, GCancellable *cancellable, GError **error)
{
g_autofree gchar *path = NULL;
g_autoptr(JsonObject) response = NULL;
JsonObject *result;
path = g_strdup_printf ("/v2/snaps/%s", name);
response = call_sync (self, "GET", path, NULL, cancellable, error);
if (response == NULL)
return NULL;
result = json_object_get_object_member (response, "result");
if (result == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to %s", path);
return NULL;
}
return json_object_ref (result);
}
JsonObject *
cc_snapd_client_get_change_sync (CcSnapdClient *self, const gchar *change_id, GCancellable *cancellable, GError **error)
{
g_autofree gchar *path = NULL;
g_autoptr(JsonObject) response = NULL;
JsonObject *result;
path = g_strdup_printf ("/v2/changes/%s", change_id);
response = call_sync (self, "GET", path, NULL, cancellable, error);
if (response == NULL)
return NULL;
result = json_object_get_object_member (response, "result");
if (result == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to %s", path);
return NULL;
}
return json_object_ref (result);
}
gboolean
cc_snapd_client_get_all_connections_sync (CcSnapdClient *self,
JsonArray **plugs, JsonArray **slots,
GCancellable *cancellable, GError **error)
{
g_autoptr(JsonObject) response = NULL;
JsonObject *result;
response = call_sync (self, "GET", "/v2/connections?select=all", NULL, cancellable, error);
if (response == NULL)
return FALSE;
result = json_object_get_object_member (response, "result");
if (result == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to /v2/connections");
return FALSE;
}
*plugs = json_array_ref (json_object_get_array_member (result, "plugs"));
*slots = json_array_ref (json_object_get_array_member (result, "slots"));
return TRUE;
}
gchar *
cc_snapd_client_connect_interface_sync (CcSnapdClient *self,
const gchar *plug_snap, const gchar *plug_name,
const gchar *slot_snap, const gchar *slot_name,
GCancellable *cancellable, GError **error)
{
return call_interfaces_sync (self, "connect", plug_snap, plug_name, slot_snap, slot_name, cancellable, error);
}
gchar *
cc_snapd_client_disconnect_interface_sync (CcSnapdClient *self,
const gchar *plug_snap, const gchar *plug_name,
const gchar *slot_snap, const gchar *slot_name,
GCancellable *cancellable, GError **error)
{
return call_interfaces_sync (self, "disconnect", plug_snap, plug_name, slot_snap, slot_name, cancellable, error);
}