1 /*
2  * Copyright (C) 2019, Matthias Clasen
3  *
4  * This file is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as
6  * published by the Free Software Foundation, version 3.0 of the
7  * License.
8  *
9  * This file is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * SPDX-License-Identifier: LGPL-3.0-only
18  */
19 
20 #include "config.h"
21 
22 #include "spawn.h"
23 
24 #define GNU_SOURCE 1
25 
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 
30 #include <glib/gstdio.h>
31 #include <gio/gunixfdlist.h>
32 
33 #include "portal-private.h"
34 
35 typedef struct {
36   XdpPortal *portal;
37   GTask *task;
38 
39   char *cwd;
40   char **argv;
41   int *fds;
42   int *map_to;
43   int n_fds;
44   char **env;
45   char **sandbox_expose;
46   char **sandbox_expose_ro;
47   XdpSpawnFlags flags;
48 } SpawnCall;
49 
50 static void
spawn_call_free(SpawnCall * call)51 spawn_call_free (SpawnCall *call)
52 {
53   g_object_unref (call->portal);
54   g_object_unref (call->task);
55 
56   g_free (call->cwd);
57   g_strfreev (call->env);
58   g_strfreev (call->sandbox_expose);
59   g_strfreev (call->sandbox_expose_ro);
60 
61   g_free (call);
62 }
63 
64 static void
spawned(GObject * bus,GAsyncResult * result,gpointer data)65 spawned (GObject      *bus,
66          GAsyncResult *result,
67          gpointer      data)
68 {
69   SpawnCall *call = data;
70   GError *error = NULL;
71   g_autoptr(GVariant) ret = NULL;
72 
73   ret = g_dbus_connection_call_with_unix_fd_list_finish (G_DBUS_CONNECTION (bus), NULL, result, &error);
74 
75   if (error)
76     g_task_return_error (call->task, error);
77   else
78     {
79       pid_t pid;
80 
81       g_variant_get (ret, "(u)", &pid);
82       g_task_return_int (call->task, (gssize)pid);
83     }
84 
85   spawn_call_free (call);
86 }
87 
88 static void
spawn_exited(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)89 spawn_exited (GDBusConnection *bus,
90               const char *sender_name,
91               const char *object_path,
92               const char *interface_name,
93               const char *signal_name,
94               GVariant *parameters,
95               gpointer data)
96 {
97   XdpPortal *portal = data;
98   guint pid;
99   guint exit_status;
100 
101   g_variant_get (parameters, "(uu)", &pid, &exit_status);
102   g_signal_emit_by_name (portal, "spawn-exited", pid, exit_status);
103 }
104 
105 static void
ensure_spawn_exited_connection(XdpPortal * portal)106 ensure_spawn_exited_connection (XdpPortal *portal)
107 {
108   if (portal->spawn_exited_signal == 0)
109     {
110       portal->spawn_exited_signal =
111          g_dbus_connection_signal_subscribe (portal->bus,
112                                              FLATPAK_PORTAL_BUS_NAME,
113                                              FLATPAK_PORTAL_INTERFACE,
114                                              "SpawnExited",
115                                              FLATPAK_PORTAL_OBJECT_PATH,
116                                              NULL,
117                                              G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
118                                              spawn_exited,
119                                              portal,
120                                              NULL);
121     }
122 }
123 
124 void
do_spawn(SpawnCall * call)125 do_spawn (SpawnCall *call)
126 {
127   g_autoptr(GUnixFDList) fd_list = NULL;
128   GVariantBuilder fds_builder;
129   GVariantBuilder env_builder;
130   GVariantBuilder opt_builder;
131 
132   ensure_spawn_exited_connection (call->portal);
133 
134   g_variant_builder_init (&fds_builder, G_VARIANT_TYPE ("a{uh}"));
135   if (call->n_fds > 0)
136     {
137       int i;
138 
139       fd_list = g_unix_fd_list_new_from_array (call->fds, call->n_fds);
140 
141       for (i = 0; i < call->n_fds; i++)
142         g_variant_builder_add (&fds_builder,"{uh}", call->map_to[i], i);
143     }
144 
145   g_variant_builder_init (&env_builder, G_VARIANT_TYPE ("a{ss}"));
146   if (call->env != NULL)
147     {
148       int i;
149 
150       for (i = 0; call->env[i]; i++)
151         {
152           g_auto(GStrv) s = g_strsplit (call->env[i], "=", 2);
153           if (s[0] && s[1])
154             g_variant_builder_add (&env_builder, "{ss}", s[0], s[1]);
155         }
156     }
157   g_variant_builder_init (&env_builder, G_VARIANT_TYPE_VARDICT);
158   if (call->sandbox_expose)
159     g_variant_builder_add (&env_builder, "{sv}", "sandbox-expose",
160                            g_variant_new_strv ((const char *const*)call->sandbox_expose, -1));
161   if (call->sandbox_expose_ro)
162     g_variant_builder_add (&env_builder, "{sv}", "sandbox-expose-ro",
163                            g_variant_new_strv ((const char *const*)call->sandbox_expose_ro, -1));
164 
165   g_dbus_connection_call_with_unix_fd_list (call->portal->bus,
166                                             FLATPAK_PORTAL_BUS_NAME,
167                                             FLATPAK_PORTAL_OBJECT_PATH,
168                                             FLATPAK_PORTAL_INTERFACE,
169                                             "Spawn",
170                                             g_variant_new ("(ay^aaya{uh}a{ss}ua{sv})",
171                                                            call->cwd,
172                                                            call->argv,
173                                                            fds_builder,
174                                                            env_builder,
175                                                            call->flags,
176                                                            opt_builder),
177                                             G_VARIANT_TYPE ("(u)"),
178                                             G_DBUS_CALL_FLAGS_NONE,
179                                             -1,
180                                             fd_list,
181                                             NULL,
182                                             spawned,
183                                             call);
184 }
185 
186 /**
187  * xdp_portal_spawn:
188  * @portal: a [class@Portal]
189  * @cwd: the cwd for the new process
190  * @argv: (array zero-terminated): the argv for the new process
191  * @fds: (array length=n_fds) (nullable): an array of open fds to pass to the new process, or `NULL`
192  * @map_to: (array length=n_fds) (nullable): an array of integers to map the @fds to, or `NULL`. Must be the same
193  *     length as @fds
194  * @n_fds: the length of @fds and @map_to arrays
195  * @env: (array zero-terminated) (nullable): an array of KEY=VALUE environment settings, or `NULL`
196  * @flags: flags influencing the spawn operation
197  * @sandbox_expose: (array zero-terminated) (nullable): paths to expose rw in the new sandbox, or `NULL`
198  * @sandbox_expose_ro: (array zero-terminated) (nullable): paths to expose ro in the new sandbox, or `NULL`
199  * @cancellable: (nullable): optional [class@Gio.Cancellable]
200  * @callback: (scope async): a callback to call when the request is done
201  * @data: (closure): data to pass to @callback
202  *
203  * Creates a new copy of the applications sandbox, and runs
204  * a process in, with the given arguments.
205  *
206  * The learn when the spawned process exits, connect to the
207  * [signal@Portal::spawn-exited] signal.
208  */
209 void
xdp_portal_spawn(XdpPortal * portal,const char * cwd,const char * const * argv,int * fds,int * map_to,int n_fds,const char * const * env,XdpSpawnFlags flags,const char * const * sandbox_expose,const char * const * sandbox_expose_ro,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)210 xdp_portal_spawn (XdpPortal            *portal,
211                   const char           *cwd,
212                   const char * const   *argv,
213                   int                  *fds,
214                   int                  *map_to,
215                   int                   n_fds,
216                   const char * const   *env,
217                   XdpSpawnFlags         flags,
218                   const char * const   *sandbox_expose,
219                   const char * const   *sandbox_expose_ro,
220                   GCancellable         *cancellable,
221                   GAsyncReadyCallback   callback,
222                   gpointer              data)
223 {
224   SpawnCall *call;
225 
226   g_return_if_fail (XDP_IS_PORTAL (portal));
227   g_return_if_fail ((flags & !(XDP_SPAWN_FLAG_CLEARENV |
228                                XDP_SPAWN_FLAG_LATEST |
229                                XDP_SPAWN_FLAG_SANDBOX |
230                                XDP_SPAWN_FLAG_NO_NETWORK |
231                                XDP_SPAWN_FLAG_WATCH)) == 0);
232 
233   call = g_new (SpawnCall, 1);
234   call->portal = g_object_ref (portal);
235   call->cwd = g_strdup (cwd);
236   call->argv = g_strdupv ((char **)argv);
237   call->fds = fds;
238   call->map_to = map_to;
239   call->n_fds = n_fds;
240   call->env = g_strdupv ((char **)env);
241   call->flags = flags;
242   call->sandbox_expose = g_strdupv ((char **)sandbox_expose);
243   call->sandbox_expose_ro = g_strdupv ((char **)sandbox_expose_ro);
244   call->task = g_task_new (portal, cancellable, callback, data);
245   g_task_set_source_tag (call->task, xdp_portal_spawn);
246 
247   do_spawn (call);
248 }
249 
250 /**
251  * xdp_portal_spawn_finish:
252  * @portal: a [class@Portal]
253  * @result: a [iface@Gio.AsyncResult]
254  * @error: return location for an error
255  *
256  * Finishes the spawn request, and returns
257  * the pid of the newly spawned process.
258  *
259  * Returns: the pid of the spawned process.
260  */
261 
262 pid_t
xdp_portal_spawn_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)263 xdp_portal_spawn_finish (XdpPortal     *portal,
264                          GAsyncResult  *result,
265                          GError       **error)
266 {
267   g_return_val_if_fail (XDP_IS_PORTAL (portal), 0);
268   g_return_val_if_fail (g_task_is_valid (result, portal), 0);
269   g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_spawn, 0);
270 
271   return (pid_t) g_task_propagate_int (G_TASK (result), error);
272 }
273 
274 /**
275  * xdp_portal_spawn_signal:
276  * @portal: a [class@Portal]
277  * @pid: the pid of the process to send a signal to
278  * @signal: the Unix signal to send (see signal(7))
279  * @to_process_group: whether to send the signal to the process
280  *     group of the process
281  *
282  * Sends a Unix signal to a process that has been spawned
283  * by [method@Portal.spawn].
284  */
285 void
xdp_portal_spawn_signal(XdpPortal * portal,pid_t pid,int signal,gboolean to_process_group)286 xdp_portal_spawn_signal (XdpPortal *portal,
287                          pid_t      pid,
288                          int        signal,
289                          gboolean   to_process_group)
290 {
291   g_return_if_fail (XDP_IS_PORTAL (portal));
292 
293   g_dbus_connection_call (portal->bus,
294                           FLATPAK_PORTAL_BUS_NAME,
295                           FLATPAK_PORTAL_OBJECT_PATH,
296                           FLATPAK_PORTAL_INTERFACE,
297                           "SpawnSignal",
298                           g_variant_new ("(uub)", (guint)pid, (guint)signal, to_process_group),
299                           NULL,
300                           G_DBUS_CALL_FLAGS_NONE,
301                           -1,
302                           NULL, NULL, NULL);
303 }
304