GCC Code Coverage Report


Directory: ./
File: panels/applications/cc-snapd-client.c
Date: 2024-05-03 09:46:52
Exec Total Coverage
Lines: 0 134 0.0%
Functions: 0 16 0.0%
Branches: 0 37 0.0%

Line Branch Exec Source
1 /* cc-snapd-client.c
2 *
3 * Copyright 2023 Canonical Ltd.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #include <libsoup/soup.h>
22
23 #include "cc-snapd-client.h"
24
25 // Unix socket that snapd communicates on.
26 #define SNAPD_SOCKET_PATH "/var/run/snapd.socket"
27
28 struct _CcSnapdClient
29 {
30 GObject parent;
31
32 // HTTP connection to snapd.
33 SoupSession *session;
34 };
35
36 G_DEFINE_TYPE (CcSnapdClient, cc_snapd_client, G_TYPE_OBJECT)
37
38 // Make an HTTP request to send to snapd.
39 static SoupMessage *
40 make_message (const gchar *method, const gchar *path, JsonNode *request_body)
41 {
42 g_autofree gchar *uri = NULL;
43 SoupMessage *msg;
44 SoupMessageHeaders *request_headers;
45
46 uri = g_strdup_printf("http://locahost%s", path);
47 msg = soup_message_new (method, uri);
48 request_headers = soup_message_get_request_headers (msg);
49 // Allow authentication via polkit.
50 soup_message_headers_append (request_headers, "X-Allow-Interaction", "true");
51 if (request_body != NULL)
52 {
53 g_autoptr(JsonGenerator) generator = NULL;
54 g_autofree gchar *body_text = NULL;
55 gsize body_length;
56 g_autoptr(GBytes) body_bytes = NULL;
57
58 generator = json_generator_new ();
59 json_generator_set_root (generator, request_body);
60 body_text = json_generator_to_data (generator, &body_length);
61 body_bytes = g_bytes_new (body_text, body_length);
62 soup_message_set_request_body_from_bytes (msg, "application/json", body_bytes);
63 }
64
65 return msg;
66 }
67
68 // Process an HTTP response recveived from snapd.
69 static JsonObject *
70 process_body (SoupMessage *msg, GBytes *body, GError **error)
71 {
72 const gchar *content_type;
73 g_autoptr(JsonParser) parser = NULL;
74 const gchar *body_data;
75 size_t body_length;
76 JsonNode *root;
77 JsonObject *response;
78 gint64 status_code;
79 g_autoptr(GError) internal_error = NULL;
80
81 content_type = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Content-Type");
82 if (g_strcmp0 (content_type, "application/json") != 0)
83 {
84 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
85 "Invalid content type %s returned", content_type);
86 return NULL;
87 }
88
89 parser = json_parser_new ();
90 body_data = g_bytes_get_data (body, &body_length);
91 if (!json_parser_load_from_data (parser, body_data, body_length, &internal_error))
92 {
93 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
94 "Failed to decode JSON content: %s", internal_error->message);
95 return NULL;
96 }
97
98 root = json_parser_get_root (parser);
99 if (!JSON_NODE_HOLDS_OBJECT (root))
100 {
101 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Returned JSON not an object");
102 return NULL;
103 }
104 response = json_node_get_object (root);
105 status_code = json_object_get_int_member (response, "status-code");
106 if (status_code != SOUP_STATUS_OK && status_code != SOUP_STATUS_ACCEPTED)
107 {
108 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid status code %" G_GINT64_FORMAT, status_code);
109 return NULL;
110 }
111
112 return json_object_ref (response);
113 }
114
115 // Send an HTTP request to snapd and process the response.
116 static JsonObject *
117 call_sync (CcSnapdClient *self,
118 const gchar *method, const gchar *path, JsonNode *request_body,
119 GCancellable *cancellable, GError **error)
120 {
121 g_autoptr(SoupMessage) msg = NULL;
122 g_autoptr(GBytes) response_body = NULL;
123
124 msg = make_message (method, path, request_body);
125 response_body = soup_session_send_and_read (self->session, msg, cancellable, error);
126 if (response_body == NULL)
127 return NULL;
128
129 return process_body (msg, response_body, error);
130 }
131
132 // Perform a snap interface action.
133 static gchar *
134 call_interfaces_sync (CcSnapdClient *self,
135 const gchar *action,
136 const gchar *plug_snap, const gchar *plug_name,
137 const gchar *slot_snap, const gchar *slot_name,
138 GCancellable *cancellable, GError **error)
139 {
140 g_autoptr(JsonBuilder) builder = NULL;
141 g_autoptr(JsonObject) response = NULL;
142 const gchar *change_id;
143
144 builder = json_builder_new();
145 json_builder_begin_object (builder);
146 json_builder_set_member_name (builder, "action");
147 json_builder_add_string_value (builder, action);
148 json_builder_set_member_name (builder, "plugs");
149 json_builder_begin_array (builder);
150 json_builder_begin_object (builder);
151 json_builder_set_member_name (builder, "snap");
152 json_builder_add_string_value (builder, plug_snap);
153 json_builder_set_member_name (builder, "plug");
154 json_builder_add_string_value (builder, plug_name);
155 json_builder_end_object (builder);
156 json_builder_end_array (builder);
157 json_builder_set_member_name (builder, "slots");
158 json_builder_begin_array (builder);
159 json_builder_begin_object (builder);
160 json_builder_set_member_name (builder, "snap");
161 json_builder_add_string_value (builder, slot_snap);
162 json_builder_set_member_name (builder, "slot");
163 json_builder_add_string_value (builder, slot_name);
164 json_builder_end_object (builder);
165 json_builder_end_array (builder);
166 json_builder_end_object (builder);
167
168 response = call_sync (self, "POST", "/v2/interfaces",
169 json_builder_get_root (builder), cancellable, error);
170 if (response == NULL)
171 return NULL;
172
173 change_id = json_object_get_string_member (response, "change");
174
175 return g_strdup (change_id);
176 }
177
178 static void
179 cc_snapd_client_dispose (GObject *object)
180 {
181 CcSnapdClient *self = CC_SNAPD_CLIENT (object);
182
183 g_clear_object(&self->session);
184
185 G_OBJECT_CLASS (cc_snapd_client_parent_class)->dispose (object);
186 }
187
188 static void
189 cc_snapd_client_class_init (CcSnapdClientClass *klass)
190 {
191 GObjectClass *object_class = G_OBJECT_CLASS (klass);
192
193 object_class->dispose = cc_snapd_client_dispose;
194 }
195
196 static void
197 cc_snapd_client_init (CcSnapdClient *self)
198 {
199 g_autoptr(GSocketAddress) address = g_unix_socket_address_new (SNAPD_SOCKET_PATH);
200 self->session = soup_session_new_with_options ("remote-connectable", address, NULL);
201 }
202
203 CcSnapdClient *
204 cc_snapd_client_new (void)
205 {
206 return CC_SNAPD_CLIENT (g_object_new (CC_TYPE_SNAPD_CLIENT, NULL));
207 }
208
209 JsonObject *
210 cc_snapd_client_get_snap_sync (CcSnapdClient *self, const gchar *name, GCancellable *cancellable, GError **error)
211 {
212 g_autofree gchar *path = NULL;
213 g_autoptr(JsonObject) response = NULL;
214 JsonObject *result;
215
216 path = g_strdup_printf ("/v2/snaps/%s", name);
217 response = call_sync (self, "GET", path, NULL, cancellable, error);
218 if (response == NULL)
219 return NULL;
220 result = json_object_get_object_member (response, "result");
221 if (result == NULL)
222 {
223 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to %s", path);
224 return NULL;
225 }
226
227 return json_object_ref (result);
228 }
229
230 JsonObject *
231 cc_snapd_client_get_change_sync (CcSnapdClient *self, const gchar *change_id, GCancellable *cancellable, GError **error)
232 {
233 g_autofree gchar *path = NULL;
234 g_autoptr(JsonObject) response = NULL;
235 JsonObject *result;
236
237 path = g_strdup_printf ("/v2/changes/%s", change_id);
238 response = call_sync (self, "GET", path, NULL, cancellable, error);
239 if (response == NULL)
240 return NULL;
241 result = json_object_get_object_member (response, "result");
242 if (result == NULL)
243 {
244 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to %s", path);
245 return NULL;
246 }
247
248 return json_object_ref (result);
249 }
250
251 gboolean
252 cc_snapd_client_get_all_connections_sync (CcSnapdClient *self,
253 JsonArray **plugs, JsonArray **slots,
254 GCancellable *cancellable, GError **error)
255 {
256 g_autoptr(JsonObject) response = NULL;
257 JsonObject *result;
258
259 response = call_sync (self, "GET", "/v2/connections?select=all", NULL, cancellable, error);
260 if (response == NULL)
261 return FALSE;
262 result = json_object_get_object_member (response, "result");
263 if (result == NULL)
264 {
265 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid response to /v2/connections");
266 return FALSE;
267 }
268
269 *plugs = json_array_ref (json_object_get_array_member (result, "plugs"));
270 *slots = json_array_ref (json_object_get_array_member (result, "slots"));
271 return TRUE;
272 }
273
274 gchar *
275 cc_snapd_client_connect_interface_sync (CcSnapdClient *self,
276 const gchar *plug_snap, const gchar *plug_name,
277 const gchar *slot_snap, const gchar *slot_name,
278 GCancellable *cancellable, GError **error)
279 {
280 return call_interfaces_sync (self, "connect", plug_snap, plug_name, slot_snap, slot_name, cancellable, error);
281 }
282
283 gchar *
284 cc_snapd_client_disconnect_interface_sync (CcSnapdClient *self,
285 const gchar *plug_snap, const gchar *plug_name,
286 const gchar *slot_snap, const gchar *slot_name,
287 GCancellable *cancellable, GError **error)
288 {
289 return call_interfaces_sync (self, "disconnect", plug_snap, plug_name, slot_snap, slot_name, cancellable, error);
290 }
291