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 |
|
|
|