1 /* gbp-deviced-runner.c
2  *
3  * Copyright 2021 James Westman <james@jwestman.net>
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 #define G_LOG_DOMAIN "gbp-deviced-runner"
22 
23 #include "../flatpak/gbp-flatpak-manifest.h"
24 
25 #include "gbp-deviced-runner.h"
26 
27 struct _GbpDevicedRunner
28 {
29   IdeRunner parent_instance;
30 
31   GbpDevicedDevice *device;
32 };
33 
34 G_DEFINE_TYPE (GbpDevicedRunner, gbp_deviced_runner, IDE_TYPE_RUNNER)
35 
36 enum {
37   PROP_0,
38   PROP_DEVICE,
39   N_PROPS
40 };
41 
42 static GParamSpec *properties [N_PROPS];
43 
44 GbpDevicedRunner *
gbp_deviced_runner_new(GbpDevicedDevice * device)45 gbp_deviced_runner_new (GbpDevicedDevice *device)
46 {
47   return g_object_new (GBP_TYPE_DEVICED_RUNNER,
48                        "device", device,
49                        NULL);
50 }
51 
52 static void
gbp_deviced_runner_finalize(GObject * object)53 gbp_deviced_runner_finalize (GObject *object)
54 {
55   GbpDevicedRunner *self = (GbpDevicedRunner *)object;
56 
57   g_clear_object (&self->device);
58 
59   G_OBJECT_CLASS (gbp_deviced_runner_parent_class)->finalize (object);
60 }
61 
62 static void
gbp_deviced_runner_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)63 gbp_deviced_runner_get_property (GObject    *object,
64                                  guint       prop_id,
65                                  GValue     *value,
66                                  GParamSpec *pspec)
67 {
68   GbpDevicedRunner *self = GBP_DEVICED_RUNNER (object);
69 
70   switch (prop_id)
71     {
72     case PROP_DEVICE:
73       g_value_set_object (value, gbp_deviced_runner_get_device (self));
74       break;
75     default:
76       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
77     }
78 }
79 
80 static void
gbp_deviced_runner_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)81 gbp_deviced_runner_set_property (GObject      *object,
82                                  guint         prop_id,
83                                  const GValue *value,
84                                  GParamSpec   *pspec)
85 {
86   GbpDevicedRunner *self = GBP_DEVICED_RUNNER (object);
87 
88   switch (prop_id)
89     {
90     case PROP_DEVICE:
91       gbp_deviced_runner_set_device (self, g_value_get_object (value));
92       break;
93     default:
94       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
95     }
96 }
97 
98 typedef struct {
99   GbpDevicedRunner *self;
100   DevdClient *client;
101   char *app_id;
102   DevdProcessService *process_service;
103   char *process_id;
104   char *pty_id;
105   int tty_fd;
106   GCancellable *cancellable;
107   gulong cancellable_handle;
108 } RunData;
109 
110 static void
run_data_free(RunData * data)111 run_data_free (RunData *data)
112 {
113   g_clear_object (&data->self);
114   g_clear_object (&data->client);
115   g_clear_object (&data->process_service);
116   g_clear_pointer (&data->app_id, g_free);
117   g_clear_pointer (&data->process_id, g_free);
118   g_clear_pointer (&data->pty_id, g_free);
119   g_cancellable_disconnect (data->cancellable, data->cancellable_handle);
120   g_free (data);
121 }
122 
123 static void run_wait_for_process_loop (IdeTask *task);
124 
125 static void
run_wait_for_process_cb(GObject * object,GAsyncResult * result,gpointer user_data)126 run_wait_for_process_cb (GObject      *object,
127                          GAsyncResult *result,
128                          gpointer      user_data)
129 {
130   DevdProcessService *process_service = (DevdProcessService *)object;
131   g_autoptr(IdeTask) task = user_data;
132   g_autoptr(GError) error = NULL;
133   RunData *data;
134   gboolean exited;
135   int exit_code;
136   int term_sig;
137   GCancellable *cancellable;
138 
139   g_assert (DEVD_IS_PROCESS_SERVICE (process_service));
140   g_assert (IDE_IS_TASK (task));
141 
142   data = ide_task_get_task_data (task);
143   cancellable = ide_task_get_cancellable (task);
144 
145   devd_process_service_wait_for_process_finish (process_service,
146                                                 result,
147                                                 &exited,
148                                                 &exit_code,
149                                                 &term_sig,
150                                                 &error);
151   if (error != NULL)
152     {
153       ide_task_return_error (task, g_steal_pointer (&error));
154       return;
155     }
156 
157   if (exited)
158     {
159       if (data->pty_id != NULL)
160         devd_process_service_destroy_pty_async (process_service,
161                                                 data->pty_id,
162                                                 cancellable,
163                                                 NULL, NULL);
164 
165       if (data->tty_fd != -1)
166         close(data->tty_fd);
167 
168       ide_task_return_boolean (task, TRUE);
169     }
170   else
171     {
172       run_wait_for_process_loop (task);
173     }
174 }
175 
176 static void
run_wait_for_process_loop(IdeTask * task)177 run_wait_for_process_loop (IdeTask *task)
178 {
179   GCancellable *cancellable;
180   RunData *data;
181 
182   data = ide_task_get_task_data (task);
183   cancellable = ide_task_get_cancellable (task);
184 
185   devd_process_service_wait_for_process_async (data->process_service,
186                                                data->process_id,
187                                                cancellable,
188                                                run_wait_for_process_cb,
189                                                g_object_ref (task));
190 }
191 
192 static void
run_cancelled_cb(GCancellable * cancellable,RunData * data)193 run_cancelled_cb (GCancellable *cancellable,
194                   RunData      *data)
195 {
196   IDE_ENTRY;
197 
198   g_assert (G_IS_CANCELLABLE (cancellable));
199 
200   if (data->process_service)
201     devd_process_service_force_exit (data->process_service, data->process_id);
202 
203   IDE_EXIT;
204 }
205 
206 static void
run_run_app_cb(GObject * object,GAsyncResult * result,gpointer user_data)207 run_run_app_cb (GObject      *object,
208                 GAsyncResult *result,
209                 gpointer      user_data)
210 {
211   DevdClient *client = (DevdClient *)object;
212   g_autoptr(IdeTask) task = user_data;
213   g_autoptr(GError) error = NULL;
214   RunData *data;
215 
216   g_assert (DEVD_IS_CLIENT (client));
217   g_assert (IDE_IS_TASK (task));
218 
219   data = ide_task_get_task_data (task);
220 
221   if (!(data->process_id = devd_client_run_app_finish (client, result, &error)))
222     {
223       ide_task_return_error (task, g_steal_pointer (&error));
224       return;
225     }
226 
227   data->cancellable = ide_task_get_cancellable (task);
228   g_cancellable_connect (data->cancellable, G_CALLBACK (run_cancelled_cb), data, NULL);
229 
230   run_wait_for_process_loop (task);
231 }
232 
233 static void
run_create_pty_cb(GObject * object,GAsyncResult * result,gpointer user_data)234 run_create_pty_cb (GObject      *object,
235                    GAsyncResult *result,
236                    gpointer      user_data)
237 {
238   RunData *data;
239   DevdProcessService *process_service = (DevdProcessService *)object;
240   g_autoptr(IdeTask) task = user_data;
241   g_autoptr(GError) error = NULL;
242 
243   IDE_ENTRY;
244 
245   g_assert (DEVD_IS_PROCESS_SERVICE (process_service));
246   g_assert (IDE_IS_TASK (task));
247 
248   data = ide_task_get_task_data (task);
249 
250   if (!(data->pty_id = devd_process_service_create_pty_finish (process_service, result, &error)))
251     {
252       ide_task_return_error (task, g_steal_pointer (&error));
253       IDE_EXIT;
254     }
255 
256   devd_client_run_app_async (data->client,
257                              "flatpak",
258                              data->app_id,
259                              data->pty_id,
260                              ide_task_get_cancellable (task),
261                              run_run_app_cb,
262                              g_object_ref (task));
263 
264   IDE_EXIT;
265 }
266 
267 static void
run_get_client_cb(GObject * object,GAsyncResult * result,gpointer user_data)268 run_get_client_cb (GObject      *object,
269                    GAsyncResult *result,
270                    gpointer      user_data)
271 {
272   RunData *data;
273   GbpDevicedDevice *device = (GbpDevicedDevice *)object;
274   g_autoptr(IdeTask) task = user_data;
275   g_autoptr(GError) error = NULL;
276   VtePty *pty;
277 
278   IDE_ENTRY;
279 
280   g_assert (GBP_IS_DEVICED_DEVICE (device));
281   g_assert (IDE_IS_TASK (task));
282 
283   data = ide_task_get_task_data (task);
284 
285   if (!(data->client = gbp_deviced_device_get_client_finish (device, result, &error)))
286     {
287       ide_task_return_error (task, g_steal_pointer (&error));
288       return;
289     }
290 
291   pty = ide_runner_get_pty (IDE_RUNNER (data->self));
292   if (pty == NULL || ide_runner_get_disable_pty (IDE_RUNNER (data->self)))
293     {
294       devd_client_run_app_async (data->client,
295                                  "flatpak",
296                                  data->app_id,
297                                  NULL,
298                                  ide_task_get_cancellable (task),
299                                  run_run_app_cb,
300                                  g_object_ref (task));
301       IDE_EXIT;
302     }
303 
304   data->process_service = devd_process_service_new (data->client, &error);
305   if (error != NULL)
306     {
307       ide_task_return_error (task, g_steal_pointer (&error));
308       IDE_EXIT;
309     }
310 
311   data->tty_fd = ide_pty_intercept_create_slave (vte_pty_get_fd (pty), TRUE);
312   devd_process_service_create_pty_async (data->process_service,
313                                          data->tty_fd,
314                                          ide_task_get_cancellable (task),
315                                          run_create_pty_cb,
316                                          g_object_ref (task));
317 
318   IDE_EXIT;
319 }
320 
321 static void
gbp_deviced_runner_run_async(IdeRunner * runner,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)322 gbp_deviced_runner_run_async (IdeRunner           *runner,
323                               GCancellable        *cancellable,
324                               GAsyncReadyCallback  callback,
325                               gpointer             user_data)
326 {
327   GbpDevicedRunner *self = (GbpDevicedRunner *)runner;
328   g_autoptr(IdeTask) task = NULL;
329   IdeContext *context;
330   IdeConfigManager *config_manager;
331   IdeConfig *config;
332   RunData *data;
333 
334   IDE_ENTRY;
335 
336   g_return_if_fail (GBP_IS_DEVICED_RUNNER (self));
337   g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
338 
339   task = ide_task_new (self, cancellable, callback, user_data);
340   ide_task_set_source_tag (task, gbp_deviced_runner_run_async);
341 
342   if (self->device == NULL)
343     {
344       ide_task_return_new_error (task,
345                                  G_IO_ERROR,
346                                  G_IO_ERROR_FAILED,
347                                  "No device set on deviced runner");
348       IDE_EXIT;
349     }
350 
351   context = ide_object_get_context (IDE_OBJECT (self->device));
352   g_assert (IDE_IS_CONTEXT (context));
353   config_manager = ide_config_manager_from_context (context);
354   config = ide_config_manager_get_current (config_manager);
355 
356   data = g_new0 (RunData, 1);
357 
358   /* GbpDevicedDeployStrategy only supports flatpak manifests, and it is the
359    * only thing that creates GbpDevicedRunners, so we should only get flatpak
360    * manifests here */
361   g_assert (GBP_IS_FLATPAK_MANIFEST (config));
362 
363   ide_task_set_task_data (task, data, run_data_free);
364 
365   data->self = g_object_ref (self);
366   data->app_id = g_strdup (ide_config_get_app_id (config));
367   data->tty_fd = -1;
368 
369   gbp_deviced_device_get_client_async (self->device,
370                                        cancellable,
371                                        run_get_client_cb,
372                                        g_object_ref (task));
373 
374   IDE_EXIT;
375 }
376 
377 static gboolean
gbp_deviced_runner_run_finish(IdeRunner * self,GAsyncResult * result,GError ** error)378 gbp_deviced_runner_run_finish (IdeRunner         *self,
379                                GAsyncResult      *result,
380                                GError           **error)
381 {
382   gboolean ret;
383 
384   IDE_ENTRY;
385 
386   g_return_val_if_fail (GBP_IS_DEVICED_RUNNER (self), FALSE);
387   g_return_val_if_fail (ide_task_is_valid (result, self), FALSE);
388 
389   ret = ide_task_propagate_boolean (IDE_TASK (result), error);
390 
391   IDE_RETURN (ret);
392 }
393 
394 static void
gbp_deviced_runner_class_init(GbpDevicedRunnerClass * klass)395 gbp_deviced_runner_class_init (GbpDevicedRunnerClass *klass)
396 {
397   GObjectClass *object_class = G_OBJECT_CLASS (klass);
398   IdeRunnerClass *runner_class = IDE_RUNNER_CLASS (klass);
399 
400   object_class->finalize = gbp_deviced_runner_finalize;
401   object_class->get_property = gbp_deviced_runner_get_property;
402   object_class->set_property = gbp_deviced_runner_set_property;
403 
404   runner_class->run_async = gbp_deviced_runner_run_async;
405   runner_class->run_finish = gbp_deviced_runner_run_finish;
406 
407   properties[PROP_DEVICE] =
408     g_param_spec_object ("device",
409                          "Device",
410                          "The device to run on",
411                          GBP_TYPE_DEVICED_DEVICE,
412                          G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
413 
414   g_object_class_install_properties (G_OBJECT_CLASS (klass), N_PROPS, properties);
415 }
416 
417 static void
gbp_deviced_runner_init(GbpDevicedRunner * self)418 gbp_deviced_runner_init (GbpDevicedRunner *self)
419 {
420 }
421 
422 GbpDevicedDevice *
gbp_deviced_runner_get_device(GbpDevicedRunner * self)423 gbp_deviced_runner_get_device (GbpDevicedRunner *self)
424 {
425   g_return_val_if_fail (GBP_IS_DEVICED_RUNNER (self), NULL);
426   return self->device;
427 }
428 
429 void
gbp_deviced_runner_set_device(GbpDevicedRunner * self,GbpDevicedDevice * device)430 gbp_deviced_runner_set_device (GbpDevicedRunner *self,
431                                GbpDevicedDevice *device)
432 {
433   g_return_if_fail (GBP_IS_DEVICED_RUNNER (self));
434   g_set_object (&self->device, device);
435 }
436