1 /* gbp-flatpak-runner.c
2  *
3  * Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
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-flatpak-runner"
22 #define G_SETTINGS_ENABLE_BACKEND
23 
24 #include <errno.h>
25 #include <gio/gsettingsbackend.h>
26 #include <glib/gi18n.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 
30 #include "gbp-flatpak-aux.h"
31 #include "gbp-flatpak-manifest.h"
32 #include "gbp-flatpak-runner.h"
33 #include "gbp-flatpak-util.h"
34 
35 struct _GbpFlatpakRunner
36 {
37   IdeRunner parent_instance;
38 
39   gchar *build_path;
40   gchar *manifest_command;
41 };
42 
43 G_DEFINE_FINAL_TYPE (GbpFlatpakRunner, gbp_flatpak_runner, IDE_TYPE_RUNNER)
44 
45 static IdeSubprocessLauncher *
46 gbp_flatpak_runner_create_launcher (IdeRunner *runner)
47 {
48   const gchar *cwd = ide_runner_get_cwd (runner);
49 
50   return g_object_new (IDE_TYPE_SUBPROCESS_LAUNCHER,
51                        "flags", 0,
52                        "cwd", cwd,
53                        NULL);
54 }
55 
56 static gboolean
57 contains_argv (IdeSubprocessLauncher *launcher,
58                const gchar           *arg)
59 {
60   const gchar * const *args;
61 
62   if (arg == NULL)
63     return TRUE;
64 
65   if (!(args = ide_subprocess_launcher_get_argv (launcher)))
66     return FALSE;
67 
68   return g_strv_contains (args, arg);
69 }
70 
71 static gchar *
72 get_project_build_dir (GbpFlatpakRunner *self)
73 {
74   IdeContext *context;
75 
76   g_assert (GBP_IS_FLATPAK_RUNNER (self));
77 
78   context = ide_object_get_context (IDE_OBJECT (self));
79   return ide_context_cache_filename (context, NULL, NULL);
80 }
81 
82 static void
83 gbp_flatpak_runner_fixup_launcher (IdeRunner             *runner,
84                                    IdeSubprocessLauncher *launcher)
85 {
86   const gchar *config_dir = gbp_flatpak_get_config_dir ();
87   GbpFlatpakRunner *self = (GbpFlatpakRunner *)runner;
88   g_autofree gchar *doc_portal = NULL;
89   g_autofree gchar *project_build_dir = NULL;
90   g_autofree gchar *project_build_dir_param = NULL;
91   IdeConfigManager *config_manager;
92   IdeConfig *config;
93   IdeEnvironment *env;
94   g_auto(GStrv) environ_ = NULL;
95   const gchar *app_id;
96   IdeContext *context;
97   guint i = 0;
98 
99   g_assert (GBP_IS_FLATPAK_RUNNER (runner));
100   g_assert (IDE_IS_SUBPROCESS_LAUNCHER (launcher));
101 
102   context = ide_object_get_context (IDE_OBJECT (self));
103   config_manager = ide_config_manager_from_context (context);
104   config = ide_config_manager_get_current (config_manager);
105   app_id = ide_config_get_app_id (config);
106 
107   doc_portal = g_strdup_printf ("--bind-mount=/run/user/%u/doc=/run/user/%u/doc/by-app/%s",
108                                 getuid (), getuid (), app_id);
109 
110   /* Get access to override installations */
111   ide_subprocess_launcher_setenv (launcher, "FLATPAK_CONFIG_DIR", config_dir, TRUE);
112 
113   ide_subprocess_launcher_insert_argv (launcher, i++, "flatpak");
114   ide_subprocess_launcher_insert_argv (launcher, i++, "build");
115   ide_subprocess_launcher_insert_argv (launcher, i++, "--with-appdir");
116   ide_subprocess_launcher_insert_argv (launcher, i++, "--allow=devel");
117   ide_subprocess_launcher_insert_argv (launcher, i++, doc_portal);
118 
119   i += gbp_flatpak_aux_apply (launcher, i);
120 
121   if (GBP_IS_FLATPAK_MANIFEST (config))
122     {
123       const gchar * const *finish_args;
124 
125       /*
126        * We cannot rely on flatpak-builder --run because it filters out
127        * some finish-args that we really care about. (Primarily so that
128        * it can preserve the integrity of the build results). So instead,
129        * we just mimic what it would do for us and ensure we pass along
130        * all the finish-args we know about.
131        */
132 
133       finish_args = gbp_flatpak_manifest_get_finish_args (GBP_FLATPAK_MANIFEST (config));
134 
135       if (finish_args != NULL)
136         {
137           for (guint j = 0; finish_args[j] != NULL; j++)
138             {
139               const gchar *arg = finish_args[j];
140 
141               if (g_str_has_prefix (arg, "--allow") ||
142                   g_str_has_prefix (arg, "--share") ||
143                   g_str_has_prefix (arg, "--socket") ||
144                   g_str_has_prefix (arg, "--filesystem") ||
145                   g_str_has_prefix (arg, "--device") ||
146                   g_str_has_prefix (arg, "--env") ||
147                   g_str_has_prefix (arg, "--system-talk") ||
148                   g_str_has_prefix (arg, "--own-name") ||
149                   g_str_has_prefix (arg, "--talk-name") ||
150                   g_str_has_prefix (arg, "--add-policy"))
151                 ide_subprocess_launcher_insert_argv (launcher, i++, arg);
152             }
153         }
154     }
155   else
156     {
157       ide_subprocess_launcher_insert_argv (launcher, i++, "--share=ipc");
158       ide_subprocess_launcher_insert_argv (launcher, i++, "--share=network");
159       ide_subprocess_launcher_insert_argv (launcher, i++, "--socket=x11");
160       ide_subprocess_launcher_insert_argv (launcher, i++, "--socket=wayland");
161     }
162 
163   ide_subprocess_launcher_insert_argv (launcher, i++, "--talk-name=org.freedesktop.portal.*");
164   ide_subprocess_launcher_insert_argv (launcher, i++, "--talk-name=org.a11y.Bus");
165 
166   /* Make sure we have access to the build directory */
167   project_build_dir = get_project_build_dir (self);
168   project_build_dir_param = g_strdup_printf ("--filesystem=%s", project_build_dir);
169   ide_subprocess_launcher_insert_argv (launcher, i++, project_build_dir_param);
170 
171   /* Proxy environment stuff to the launcher */
172   if ((env = ide_runner_get_environment (runner)) &&
173       (environ_ = ide_environment_get_environ (env)))
174     {
175       for (guint j = 0; environ_[j]; j++)
176         {
177           g_autofree gchar *arg = g_strdup_printf ("--env=%s", environ_[j]);
178 
179           if (!contains_argv (launcher, arg))
180             ide_subprocess_launcher_insert_argv (launcher, i++, arg);
181         }
182     }
183 
184   /* Disable G_MESSAGES_DEBUG as it could cause 'flatpak build' to spew info
185    * and mess up systems that need a clean stdin/stdout/stderr.
186    */
187   ide_subprocess_launcher_setenv (launcher, "G_MESSAGES_DEBUG", NULL, TRUE);
188 
189   ide_subprocess_launcher_insert_argv (launcher, i++, self->build_path);
190 }
191 
192 GbpFlatpakRunner *
193 gbp_flatpak_runner_new (IdeContext     *context,
194                         const gchar    *build_path,
195                         IdeBuildTarget *build_target,
196                         const gchar    *manifest_command)
197 {
198   GbpFlatpakRunner *self;
199 
200   g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
201   g_return_val_if_fail (!build_target || IDE_IS_BUILD_TARGET (build_target), NULL);
202   g_return_val_if_fail (build_target || manifest_command, NULL);
203 
204   self = g_object_new (GBP_TYPE_FLATPAK_RUNNER, NULL);
205   self->build_path = g_strdup (build_path);
206   self->manifest_command = g_strdup (manifest_command);
207 
208   if (build_target == NULL)
209     {
210       ide_runner_append_argv (IDE_RUNNER (self), manifest_command);
211     }
212   else
213     {
214       g_auto(GStrv) argv = ide_build_target_get_argv (build_target);
215 
216       if (argv != NULL)
217         {
218           for (guint i = 0; argv[i]; i++)
219             ide_runner_append_argv (IDE_RUNNER (self), argv[i]);
220         }
221 
222       ide_runner_set_build_target (IDE_RUNNER (self), build_target);
223     }
224 
225   return g_steal_pointer (&self);
226 }
227 
228 static void
229 override_settings (GbpFlatpakRunner *self)
230 {
231   g_autoptr(GSettingsBackend) backend = NULL;
232   g_autoptr(GSettings) settings = NULL;
233   g_autofree char *filename = NULL;
234   IdeConfigManager *config_manager;
235   IdeContext *context;
236   const char *app_id;
237   IdeConfig *config;
238 
239   g_assert (GBP_IS_FLATPAK_RUNNER (self));
240 
241   if (!(context = ide_object_get_context (IDE_OBJECT (self))) ||
242       !(config_manager = ide_config_manager_from_context (context)) ||
243       !(config = ide_config_manager_get_current (config_manager)) ||
244       !(app_id = ide_config_get_app_id (config)))
245     return;
246 
247   filename = g_build_filename (g_get_home_dir (), ".var", "app", app_id,
248                                "config", "glib-2.0", "settings", "keyfile",
249                                NULL);
250   backend = g_keyfile_settings_backend_new (filename, "/", NULL);
251   settings = g_settings_new_with_backend ("org.gtk.Settings.Debug", backend);
252 
253   g_settings_set_boolean (settings, "enable-inspector-keybinding", TRUE);
254 }
255 
256 static void
257 gbp_flatpak_runner_run_async (IdeRunner           *runner,
258                               GCancellable        *cancellable,
259                               GAsyncReadyCallback  callback,
260                               gpointer             user_data)
261 {
262   GbpFlatpakRunner *self = (GbpFlatpakRunner *)runner;
263 
264   g_assert (GBP_IS_FLATPAK_RUNNER (self));
265   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
266 
267   override_settings (self);
268 
269   IDE_RUNNER_CLASS (gbp_flatpak_runner_parent_class)->run_async (runner, cancellable, callback, user_data);
270 }
271 
272 static void
273 gbp_flatpak_runner_finalize (GObject *object)
274 {
275   GbpFlatpakRunner *self = (GbpFlatpakRunner *)object;
276 
277   g_clear_pointer (&self->build_path, g_free);
278   g_clear_pointer (&self->manifest_command, g_free);
279 
280   G_OBJECT_CLASS (gbp_flatpak_runner_parent_class)->finalize (object);
281 }
282 
283 static void
284 gbp_flatpak_runner_class_init (GbpFlatpakRunnerClass *klass)
285 {
286   GObjectClass *object_class = G_OBJECT_CLASS (klass);
287   IdeRunnerClass *runner_class = IDE_RUNNER_CLASS (klass);
288 
289   object_class->finalize = gbp_flatpak_runner_finalize;
290 
291   runner_class->create_launcher = gbp_flatpak_runner_create_launcher;
292   runner_class->fixup_launcher = gbp_flatpak_runner_fixup_launcher;
293   runner_class->run_async = gbp_flatpak_runner_run_async;
294 }
295 
296 static void
297 gbp_flatpak_runner_init (GbpFlatpakRunner *self)
298 {
299   ide_runner_set_run_on_host (IDE_RUNNER (self), TRUE);
300   ide_runner_set_clear_env (IDE_RUNNER (self), FALSE);
301 }
302