1 /* ide-subprocess-launcher.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 "ide-subprocess-launcher"
22
23 #include "config.h"
24
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <libide-core.h>
28 #include <signal.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/ioctl.h>
32 #ifdef __linux__
33 # include <sys/prctl.h>
34 #endif
35 #include <sys/stat.h>
36 #include <sys/types.h>
37 #include <unistd.h>
38
39 #include "ide-environment.h"
40 #include "ide-environment-variable.h"
41 #include "ide-flatpak-subprocess-private.h"
42 #include "ide-simple-subprocess-private.h"
43 #include "ide-subprocess-launcher.h"
44
45 #define is_flatpak() (ide_get_process_kind() == IDE_PROCESS_KIND_FLATPAK)
46
47 typedef struct
48 {
49 GSubprocessFlags flags;
50
51 GPtrArray *argv;
52 gchar *cwd;
53 gchar **environ;
54 GArray *fd_mapping;
55 gchar *stdout_file_path;
56
57 gint stdin_fd;
58 gint stdout_fd;
59 gint stderr_fd;
60
61 guint run_on_host : 1;
62 guint clear_env : 1;
63 } IdeSubprocessLauncherPrivate;
64
65 typedef struct
66 {
67 gint source_fd;
68 gint dest_fd;
69 } FdMapping;
70
71 G_DEFINE_TYPE_WITH_PRIVATE (IdeSubprocessLauncher, ide_subprocess_launcher, G_TYPE_OBJECT)
72
73 enum {
74 PROP_0,
75 PROP_CLEAR_ENV,
76 PROP_CWD,
77 PROP_ENVIRON,
78 PROP_FLAGS,
79 PROP_RUN_ON_HOST,
80 N_PROPS
81 };
82
83 static GParamSpec *properties [N_PROPS];
84
85 static void
child_setup_func(gpointer data)86 child_setup_func (gpointer data)
87 {
88 #ifdef G_OS_UNIX
89 /*
90 * TODO: Check on FreeBSD to see if the process group id is the same as
91 * the owning process. If not, our kill() signal might not work
92 * as expected.
93 */
94
95 setsid ();
96 setpgid (0, 0);
97
98 #ifdef __linux__
99 /*
100 * If we were spawned from the main thread, then we can setup the
101 * PR_SET_PDEATHSIG and know that when this thread exits that the
102 * child will get a kill sig.
103 */
104 if (data != NULL)
105 prctl (PR_SET_PDEATHSIG, SIGKILL);
106 #endif
107
108 if (isatty (STDIN_FILENO))
109 {
110 if (ioctl (STDIN_FILENO, TIOCSCTTY, 0) != 0)
111 g_warning ("Failed to setup TIOCSCTTY on stdin: %s",
112 g_strerror (errno));
113 }
114 #endif
115 }
116
117 static void
ide_subprocess_launcher_kill_process_group(GCancellable * cancellable,GSubprocess * subprocess)118 ide_subprocess_launcher_kill_process_group (GCancellable *cancellable,
119 GSubprocess *subprocess)
120 {
121 #ifdef G_OS_UNIX
122 const gchar *ident;
123 pid_t pid;
124
125 IDE_ENTRY;
126
127 g_assert (G_IS_CANCELLABLE (cancellable));
128 g_assert (G_IS_SUBPROCESS (subprocess));
129
130 /*
131 * This will send SIGKILL to all processes in the process group that
132 * was created for our subprocess using setsid().
133 */
134
135 if (NULL != (ident = g_subprocess_get_identifier (subprocess)))
136 {
137 g_debug ("Killing process group %s due to cancellation", ident);
138 pid = atoi (ident);
139 kill (-pid, SIGKILL);
140 }
141
142 g_signal_handlers_disconnect_by_func (cancellable,
143 G_CALLBACK (ide_subprocess_launcher_kill_process_group),
144 subprocess);
145
146 IDE_EXIT;
147 #else
148 # error "Your platform is not yet supported"
149 #endif
150 }
151
152 static void
ide_subprocess_launcher_kill_host_process(GCancellable * cancellable,IdeSubprocess * subprocess)153 ide_subprocess_launcher_kill_host_process (GCancellable *cancellable,
154 IdeSubprocess *subprocess)
155 {
156 IDE_ENTRY;
157
158 g_assert (G_IS_CANCELLABLE (cancellable));
159 g_assert (IDE_IS_FLATPAK_SUBPROCESS (subprocess));
160
161 g_signal_handlers_disconnect_by_func (cancellable,
162 G_CALLBACK (ide_subprocess_launcher_kill_host_process),
163 subprocess);
164
165 ide_subprocess_force_exit (subprocess);
166
167 IDE_EXIT;
168 }
169
170 IdeSubprocessLauncher *
ide_subprocess_launcher_new(GSubprocessFlags flags)171 ide_subprocess_launcher_new (GSubprocessFlags flags)
172 {
173 return g_object_new (IDE_TYPE_SUBPROCESS_LAUNCHER,
174 "flags", flags,
175 NULL);
176 }
177
178 static gboolean
should_use_flatpak_process(IdeSubprocessLauncher * self)179 should_use_flatpak_process (IdeSubprocessLauncher *self)
180 {
181 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
182
183 g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
184
185 if (g_getenv ("IDE_USE_FLATPAK_SUBPROCESS") != NULL)
186 return TRUE;
187
188 if (!priv->run_on_host)
189 return FALSE;
190
191 return is_flatpak ();
192 }
193
194 static void
ide_subprocess_launcher_spawn_host_worker(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)195 ide_subprocess_launcher_spawn_host_worker (GTask *task,
196 gpointer source_object,
197 gpointer task_data,
198 GCancellable *cancellable)
199 {
200 IdeSubprocessLauncher *self = source_object;
201 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
202 g_autoptr(IdeSubprocess) process = NULL;
203 g_autoptr(GError) error = NULL;
204 g_autoptr(GArray) fds = NULL;
205 gint stdin_fd = -1;
206 gint stdout_fd = -1;
207 gint stderr_fd = -1;
208
209 IDE_ENTRY;
210
211 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
212
213 #ifdef IDE_ENABLE_TRACE
214 {
215 g_autofree gchar *str = NULL;
216 g_autofree gchar *env = NULL;
217 str = g_strjoinv (" ", (gchar **)priv->argv->pdata);
218 env = priv->environ ? g_strjoinv (" ", priv->environ) : g_strdup ("");
219 IDE_TRACE_MSG ("Launching '%s' with environment %s %s parent environment",
220 str, env, priv->clear_env ? "clearing" : "inheriting");
221 }
222 #endif
223
224 fds = g_steal_pointer (&priv->fd_mapping);
225
226 if (priv->stdin_fd != -1)
227 stdin_fd = dup (priv->stdin_fd);
228
229 if (priv->stdout_fd != -1)
230 stdout_fd = dup (priv->stdout_fd);
231 else if (priv->stdout_file_path != NULL)
232 stdout_fd = open (priv->stdout_file_path, O_WRONLY);
233
234 if (priv->stderr_fd != -1)
235 stderr_fd = dup (priv->stderr_fd);
236
237 process = _ide_flatpak_subprocess_new (priv->cwd,
238 (const gchar * const *)priv->argv->pdata,
239 (const gchar * const *)priv->environ,
240 priv->flags,
241 priv->clear_env,
242 stdin_fd,
243 stdout_fd,
244 stderr_fd,
245 fds ? (gpointer)fds->data : NULL,
246 fds ? fds->len : 0,
247 cancellable,
248 &error);
249
250 if (process == NULL)
251 {
252 g_task_return_error (task, g_steal_pointer (&error));
253 IDE_EXIT;
254 }
255
256 if (cancellable != NULL)
257 {
258 g_signal_connect_object (cancellable,
259 "cancelled",
260 G_CALLBACK (ide_subprocess_launcher_kill_host_process),
261 process,
262 0);
263 }
264
265 g_task_return_pointer (task, g_steal_pointer (&process), g_object_unref);
266
267 IDE_EXIT;
268 }
269
270 static void
ide_subprocess_launcher_spawn_worker(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)271 ide_subprocess_launcher_spawn_worker (GTask *task,
272 gpointer source_object,
273 gpointer task_data,
274 GCancellable *cancellable)
275 {
276 IdeSubprocessLauncher *self = source_object;
277 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
278 g_autoptr(GSubprocessLauncher) launcher = NULL;
279 g_autoptr(GSubprocess) real = NULL;
280 g_autoptr(IdeSubprocess) wrapped = NULL;
281 g_autoptr(GError) error = NULL;
282 gpointer child_data = NULL;
283
284 IDE_ENTRY;
285
286 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
287
288 if (IDE_IS_MAIN_THREAD ())
289 child_data = GUINT_TO_POINTER (TRUE);
290
291 {
292 g_autofree gchar *str = NULL;
293 g_autofree gchar *env = NULL;
294
295 str = g_strjoinv (" ", (gchar **)priv->argv->pdata);
296 env = priv->environ ? g_strjoinv (" ", priv->environ) : g_strdup ("");
297
298 g_debug ("Launching '%s' from directory '%s' with environment %s %s parent environment",
299 str, priv->cwd, env, priv->clear_env ? "clearing" : "inheriting");
300 }
301
302 launcher = g_subprocess_launcher_new (priv->flags);
303 g_subprocess_launcher_set_child_setup (launcher, child_setup_func, child_data, NULL);
304 g_subprocess_launcher_set_cwd (launcher, priv->cwd);
305
306 if (priv->stdout_file_path != NULL)
307 g_subprocess_launcher_set_stdout_file_path (launcher, priv->stdout_file_path);
308
309 if (priv->stdin_fd != -1)
310 {
311 g_subprocess_launcher_take_stdin_fd (launcher, priv->stdin_fd);
312 priv->stdin_fd = -1;
313 }
314
315 if (priv->stdout_fd != -1)
316 {
317 g_subprocess_launcher_take_stdout_fd (launcher, priv->stdout_fd);
318 priv->stdout_fd = -1;
319 }
320
321 if (priv->stderr_fd != -1)
322 {
323 g_subprocess_launcher_take_stderr_fd (launcher, priv->stderr_fd);
324 priv->stderr_fd = -1;
325 }
326
327 if (priv->fd_mapping != NULL)
328 {
329 g_autoptr(GArray) ar = g_steal_pointer (&priv->fd_mapping);
330
331 for (guint i = 0; i < ar->len; i++)
332 {
333 const FdMapping *map = &g_array_index (ar, FdMapping, i);
334
335 g_subprocess_launcher_take_fd (launcher, map->source_fd, map->dest_fd);
336 }
337 }
338
339 /*
340 * GSubprocessLauncher starts by inheriting the current environment.
341 * So if clear-env is set, we need to unset those environment variables.
342 * Simply setting the environ to NULL doesn't work, because glib uses
343 * execv rather than execve in that case.
344 */
345 if (priv->clear_env)
346 {
347 gchar *envp[] = { NULL };
348 g_subprocess_launcher_set_environ (launcher, envp);
349 }
350
351 /*
352 * Now override any environment variables that were set using
353 * ide_subprocess_launcher_setenv() or ide_subprocess_launcher_set_environ().
354 */
355 if (priv->environ != NULL)
356 {
357 for (guint i = 0; priv->environ[i] != NULL; i++)
358 {
359 const gchar *pair = priv->environ[i];
360 const gchar *eq = strchr (pair, '=');
361 g_autofree gchar *key = g_strndup (pair, eq - pair);
362 const gchar *val = eq ? eq + 1 : NULL;
363
364 g_subprocess_launcher_setenv (launcher, key, val, TRUE);
365 }
366 }
367
368 real = g_subprocess_launcher_spawnv (launcher,
369 (const gchar * const *)priv->argv->pdata,
370 &error);
371
372 if (real == NULL)
373 {
374 g_task_return_error (task, g_steal_pointer (&error));
375 IDE_EXIT;
376 }
377
378 if (cancellable != NULL)
379 {
380 g_signal_connect_object (cancellable,
381 "cancelled",
382 G_CALLBACK (ide_subprocess_launcher_kill_process_group),
383 real,
384 0);
385 }
386
387 wrapped = ide_simple_subprocess_new (real);
388
389 g_task_return_pointer (task, g_steal_pointer (&wrapped), g_object_unref);
390
391 IDE_EXIT;
392 }
393
394 static IdeSubprocess *
ide_subprocess_launcher_real_spawn(IdeSubprocessLauncher * self,GCancellable * cancellable,GError ** error)395 ide_subprocess_launcher_real_spawn (IdeSubprocessLauncher *self,
396 GCancellable *cancellable,
397 GError **error)
398 {
399 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
400 g_autoptr(GTask) task = NULL;
401 IdeSubprocess *ret;
402 GError *local_error = NULL;
403
404 g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
405 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
406
407 task = g_task_new (self, cancellable, NULL, NULL);
408 g_task_set_source_tag (task, ide_subprocess_launcher_real_spawn);
409
410 if (priv->clear_env || (is_flatpak () && priv->run_on_host))
411 {
412 /*
413 * Many things break without at least PATH, HOME, etc. being set.
414 * The GbpFlatpakSubprocessLauncher will also try to set PATH so
415 * that it can get /app/bin too. Since it chains up to us, we wont
416 * overwrite PATH in that case (which is what we want).
417 */
418 ide_subprocess_launcher_setenv (self, "PATH", SAFE_PATH, FALSE);
419 ide_subprocess_launcher_setenv (self, "HOME", g_get_home_dir (), FALSE);
420 ide_subprocess_launcher_setenv (self, "USER", g_get_user_name (), FALSE);
421 ide_subprocess_launcher_setenv (self, "LANG", g_getenv ("LANG"), FALSE);
422 }
423
424 if (should_use_flatpak_process (self))
425 ide_subprocess_launcher_spawn_host_worker (task, self, NULL, cancellable);
426 else
427 ide_subprocess_launcher_spawn_worker (task, self, NULL, cancellable);
428
429 ret = g_task_propagate_pointer (task, &local_error);
430
431 if (!ret && !local_error)
432 local_error = g_error_new (G_IO_ERROR,
433 G_IO_ERROR_FAILED,
434 "An unkonwn error occurred while spawning");
435
436 if (local_error != NULL)
437 g_propagate_error (error, g_steal_pointer (&local_error));
438
439 return g_steal_pointer (&ret);
440 }
441
442 static void
ide_subprocess_launcher_finalize(GObject * object)443 ide_subprocess_launcher_finalize (GObject *object)
444 {
445 IdeSubprocessLauncher *self = (IdeSubprocessLauncher *)object;
446 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
447
448 if (priv->fd_mapping != NULL)
449 {
450 for (guint i = 0; i < priv->fd_mapping->len; i++)
451 {
452 FdMapping *map = &g_array_index (priv->fd_mapping, FdMapping, i);
453
454 if (map->source_fd != -1)
455 close (map->source_fd);
456 }
457
458 g_clear_pointer (&priv->fd_mapping, g_array_unref);
459 }
460
461 g_clear_pointer (&priv->argv, g_ptr_array_unref);
462 g_clear_pointer (&priv->cwd, g_free);
463 g_clear_pointer (&priv->environ, g_strfreev);
464 g_clear_pointer (&priv->stdout_file_path, g_free);
465
466 if (priv->stdin_fd != -1)
467 {
468 close (priv->stdin_fd);
469 priv->stdin_fd = -1;
470 }
471
472 if (priv->stdout_fd != -1)
473 {
474 close (priv->stdout_fd);
475 priv->stdout_fd = -1;
476 }
477
478 if (priv->stderr_fd != -1)
479 {
480 close (priv->stderr_fd);
481 priv->stderr_fd = -1;
482 }
483
484 G_OBJECT_CLASS (ide_subprocess_launcher_parent_class)->finalize (object);
485 }
486
487 static void
ide_subprocess_launcher_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)488 ide_subprocess_launcher_get_property (GObject *object,
489 guint prop_id,
490 GValue *value,
491 GParamSpec *pspec)
492 {
493 IdeSubprocessLauncher *self = IDE_SUBPROCESS_LAUNCHER (object);
494
495 switch (prop_id)
496 {
497 case PROP_CLEAR_ENV:
498 g_value_set_boolean (value, ide_subprocess_launcher_get_clear_env (self));
499 break;
500
501 case PROP_CWD:
502 g_value_set_string (value, ide_subprocess_launcher_get_cwd (self));
503 break;
504
505 case PROP_FLAGS:
506 g_value_set_flags (value, ide_subprocess_launcher_get_flags (self));
507 break;
508
509 case PROP_ENVIRON:
510 g_value_set_boxed (value, ide_subprocess_launcher_get_environ (self));
511 break;
512
513 case PROP_RUN_ON_HOST:
514 g_value_set_boolean (value, ide_subprocess_launcher_get_run_on_host (self));
515 break;
516
517 default:
518 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
519 }
520 }
521
522 static void
ide_subprocess_launcher_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)523 ide_subprocess_launcher_set_property (GObject *object,
524 guint prop_id,
525 const GValue *value,
526 GParamSpec *pspec)
527 {
528 IdeSubprocessLauncher *self = IDE_SUBPROCESS_LAUNCHER (object);
529
530 switch (prop_id)
531 {
532 case PROP_CLEAR_ENV:
533 ide_subprocess_launcher_set_clear_env (self, g_value_get_boolean (value));
534 break;
535
536 case PROP_CWD:
537 ide_subprocess_launcher_set_cwd (self, g_value_get_string (value));
538 break;
539
540 case PROP_FLAGS:
541 ide_subprocess_launcher_set_flags (self, g_value_get_flags (value));
542 break;
543
544 case PROP_ENVIRON:
545 ide_subprocess_launcher_set_environ (self, g_value_get_boxed (value));
546 break;
547
548 case PROP_RUN_ON_HOST:
549 ide_subprocess_launcher_set_run_on_host (self, g_value_get_boolean (value));
550 break;
551
552 default:
553 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
554 }
555 }
556
557 static void
ide_subprocess_launcher_class_init(IdeSubprocessLauncherClass * klass)558 ide_subprocess_launcher_class_init (IdeSubprocessLauncherClass *klass)
559 {
560 GObjectClass *object_class = G_OBJECT_CLASS (klass);
561
562 object_class->finalize = ide_subprocess_launcher_finalize;
563 object_class->get_property = ide_subprocess_launcher_get_property;
564 object_class->set_property = ide_subprocess_launcher_set_property;
565
566 klass->spawn = ide_subprocess_launcher_real_spawn;
567
568 properties [PROP_CLEAR_ENV] =
569 g_param_spec_boolean ("clean-env",
570 "Clear Environment",
571 "If the environment should be cleared before setting environment variables.",
572 FALSE,
573 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
574
575 properties [PROP_CWD] =
576 g_param_spec_string ("cwd",
577 "Current Working Directory",
578 "Current Working Directory",
579 NULL,
580 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
581
582 properties [PROP_FLAGS] =
583 g_param_spec_flags ("flags",
584 "Flags",
585 "Flags",
586 G_TYPE_SUBPROCESS_FLAGS,
587 G_SUBPROCESS_FLAGS_NONE,
588 (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
589
590 properties [PROP_ENVIRON] =
591 g_param_spec_boxed ("environ",
592 "Environ",
593 "Environ",
594 G_TYPE_STRV,
595 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
596
597 properties [PROP_RUN_ON_HOST] =
598 g_param_spec_boolean ("run-on-host",
599 "Run on Host",
600 "Run on Host",
601 FALSE,
602 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
603
604 g_object_class_install_properties (object_class, N_PROPS, properties);
605 }
606
607 static void
ide_subprocess_launcher_init(IdeSubprocessLauncher * self)608 ide_subprocess_launcher_init (IdeSubprocessLauncher *self)
609 {
610 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
611
612 priv->clear_env = TRUE;
613
614 priv->stdin_fd = -1;
615 priv->stdout_fd = -1;
616 priv->stderr_fd = -1;
617
618 priv->argv = g_ptr_array_new_with_free_func (g_free);
619 g_ptr_array_add (priv->argv, NULL);
620
621 priv->cwd = g_strdup (".");
622 /* Prevent inheritance of G_MESSAGES_DEBUG because it brings a lot of problems with IPC
623 * over stdout/stdin because all the debug messages would go to stdout, which means
624 * that the connection would be closed because of invalid data. If needed it can still
625 * be set back if needed, but at least it's a good default.
626 */
627 ide_subprocess_launcher_setenv (self, "G_MESSAGES_DEBUG", "", TRUE);
628 }
629
630 void
ide_subprocess_launcher_set_flags(IdeSubprocessLauncher * self,GSubprocessFlags flags)631 ide_subprocess_launcher_set_flags (IdeSubprocessLauncher *self,
632 GSubprocessFlags flags)
633 {
634 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
635
636 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
637
638 if (flags != priv->flags)
639 {
640 priv->flags = flags;
641 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FLAGS]);
642 }
643 }
644
645 GSubprocessFlags
ide_subprocess_launcher_get_flags(IdeSubprocessLauncher * self)646 ide_subprocess_launcher_get_flags (IdeSubprocessLauncher *self)
647 {
648 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
649
650 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), 0);
651
652 return priv->flags;
653 }
654
655 const gchar * const *
ide_subprocess_launcher_get_environ(IdeSubprocessLauncher * self)656 ide_subprocess_launcher_get_environ (IdeSubprocessLauncher *self)
657 {
658 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
659
660 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
661
662 return (const gchar * const *)priv->environ;
663 }
664
665 /**
666 * ide_subprocess_launcher_set_environ:
667 * @self: an #IdeSubprocessLauncher
668 * @environ_: (array zero-terminated=1) (element-type utf8) (nullable): the list
669 * of environment variables to set
670 *
671 * Replace the environment variables by a new list of variables.
672 *
673 * Since: 3.32
674 */
675 void
ide_subprocess_launcher_set_environ(IdeSubprocessLauncher * self,const gchar * const * environ_)676 ide_subprocess_launcher_set_environ (IdeSubprocessLauncher *self,
677 const gchar * const *environ_)
678 {
679 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
680
681 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
682
683 if (priv->environ != (gchar **)environ_)
684 {
685 g_strfreev (priv->environ);
686 priv->environ = g_strdupv ((gchar **)environ_);
687 }
688 }
689
690 const gchar *
ide_subprocess_launcher_getenv(IdeSubprocessLauncher * self,const gchar * key)691 ide_subprocess_launcher_getenv (IdeSubprocessLauncher *self,
692 const gchar *key)
693 {
694 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
695
696 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
697 g_return_val_if_fail (key != NULL, NULL);
698
699 return g_environ_getenv (priv->environ, key);
700 }
701
702 void
ide_subprocess_launcher_setenv(IdeSubprocessLauncher * self,const gchar * key,const gchar * value,gboolean replace)703 ide_subprocess_launcher_setenv (IdeSubprocessLauncher *self,
704 const gchar *key,
705 const gchar *value,
706 gboolean replace)
707 {
708 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
709
710 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
711 g_return_if_fail (key != NULL);
712
713 if (value != NULL)
714 priv->environ = g_environ_setenv (priv->environ, key, value, replace);
715 else
716 priv->environ = g_environ_unsetenv (priv->environ, key);
717 }
718
719 void
ide_subprocess_launcher_push_argv(IdeSubprocessLauncher * self,const gchar * argv)720 ide_subprocess_launcher_push_argv (IdeSubprocessLauncher *self,
721 const gchar *argv)
722 {
723 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
724
725 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
726 g_return_if_fail (argv != NULL);
727
728 g_ptr_array_index (priv->argv, priv->argv->len - 1) = g_strdup (argv);
729 g_ptr_array_add (priv->argv, NULL);
730 }
731
732 /**
733 * ide_subprocess_launcher_spawn:
734 *
735 * Synchronously spawn a process using the internal state.
736 *
737 * Returns: (transfer full): an #IdeSubprocess or %NULL upon error.
738 *
739 * Since: 3.32
740 */
741 IdeSubprocess *
ide_subprocess_launcher_spawn(IdeSubprocessLauncher * self,GCancellable * cancellable,GError ** error)742 ide_subprocess_launcher_spawn (IdeSubprocessLauncher *self,
743 GCancellable *cancellable,
744 GError **error)
745 {
746 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
747 g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
748
749 return IDE_SUBPROCESS_LAUNCHER_GET_CLASS (self)->spawn (self, cancellable, error);
750 }
751
752 void
ide_subprocess_launcher_set_cwd(IdeSubprocessLauncher * self,const gchar * cwd)753 ide_subprocess_launcher_set_cwd (IdeSubprocessLauncher *self,
754 const gchar *cwd)
755 {
756 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
757
758 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
759
760 if (ide_str_empty0 (cwd))
761 cwd = ".";
762
763 if (!ide_str_equal0 (priv->cwd, cwd))
764 {
765 g_free (priv->cwd);
766 priv->cwd = g_strdup (cwd);
767 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CWD]);
768 }
769 }
770
771 const gchar *
ide_subprocess_launcher_get_cwd(IdeSubprocessLauncher * self)772 ide_subprocess_launcher_get_cwd (IdeSubprocessLauncher *self)
773 {
774 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
775
776 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
777
778 return priv->cwd;
779 }
780
781 void
ide_subprocess_launcher_overlay_environment(IdeSubprocessLauncher * self,IdeEnvironment * environment)782 ide_subprocess_launcher_overlay_environment (IdeSubprocessLauncher *self,
783 IdeEnvironment *environment)
784 {
785 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
786 g_return_if_fail (!environment || IDE_IS_ENVIRONMENT (environment));
787
788 if (environment != NULL)
789 {
790 guint n_items = g_list_model_get_n_items (G_LIST_MODEL (environment));
791
792 for (guint i = 0; i < n_items; i++)
793 {
794 g_autoptr(IdeEnvironmentVariable) var = NULL;
795 const gchar *key;
796 const gchar *value;
797
798 var = g_list_model_get_item (G_LIST_MODEL (environment), i);
799 key = ide_environment_variable_get_key (var);
800 value = ide_environment_variable_get_value (var);
801
802 if (!ide_str_empty0 (key))
803 ide_subprocess_launcher_setenv (self, key, value ?: "", TRUE);
804 }
805 }
806 }
807
808 /**
809 * ide_subprocess_launcher_push_args:
810 * @self: an #IdeSubprocessLauncher
811 * @args: (array zero-terminated=1) (element-type utf8) (nullable): the arguments
812 *
813 * This function is semantically identical to calling ide_subprocess_launcher_push_argv()
814 * for each element of @args.
815 *
816 * If @args is %NULL, this function does nothing.
817 *
818 * Since: 3.32
819 */
820 void
ide_subprocess_launcher_push_args(IdeSubprocessLauncher * self,const gchar * const * args)821 ide_subprocess_launcher_push_args (IdeSubprocessLauncher *self,
822 const gchar * const *args)
823 {
824 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
825
826 if (args != NULL)
827 {
828 for (guint i = 0; args [i] != NULL; i++)
829 ide_subprocess_launcher_push_argv (self, args [i]);
830 }
831 }
832
833 gchar *
ide_subprocess_launcher_pop_argv(IdeSubprocessLauncher * self)834 ide_subprocess_launcher_pop_argv (IdeSubprocessLauncher *self)
835 {
836 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
837 gchar *ret = NULL;
838
839 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
840
841 if (priv->argv->len > 1)
842 {
843 g_assert (g_ptr_array_index (priv->argv, priv->argv->len - 1) == NULL);
844
845 ret = g_ptr_array_index (priv->argv, priv->argv->len - 2);
846 g_ptr_array_index (priv->argv, priv->argv->len - 2) = NULL;
847 g_ptr_array_set_size (priv->argv, priv->argv->len - 1);
848 }
849
850 return ret;
851 }
852
853 /**
854 * ide_subprocess_launcher_get_run_on_host:
855 *
856 * Gets if the process should be executed on the host system. This might be
857 * useful for situations where running in a contained environment is not
858 * sufficient to perform the given task.
859 *
860 * Currently, only flatpak is supported for breaking out of the containment
861 * zone and requires the application was built with --allow=devel.
862 *
863 * Returns: %TRUE if the process should be executed outside the containment zone.
864 *
865 * Since: 3.32
866 */
867 gboolean
ide_subprocess_launcher_get_run_on_host(IdeSubprocessLauncher * self)868 ide_subprocess_launcher_get_run_on_host (IdeSubprocessLauncher *self)
869 {
870 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
871
872 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), FALSE);
873
874 return priv->run_on_host;
875 }
876
877 /**
878 * ide_subprocess_launcher_set_run_on_host:
879 *
880 * Sets the #IdeSubprocessLauncher:run-on-host property. See
881 * ide_subprocess_launcher_get_run_on_host() for more information.
882 *
883 * Since: 3.32
884 */
885 void
ide_subprocess_launcher_set_run_on_host(IdeSubprocessLauncher * self,gboolean run_on_host)886 ide_subprocess_launcher_set_run_on_host (IdeSubprocessLauncher *self,
887 gboolean run_on_host)
888 {
889 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
890
891 run_on_host = !!run_on_host;
892
893 if (priv->run_on_host != run_on_host)
894 {
895 priv->run_on_host = run_on_host;
896 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUN_ON_HOST]);
897 }
898 }
899
900 gboolean
ide_subprocess_launcher_get_clear_env(IdeSubprocessLauncher * self)901 ide_subprocess_launcher_get_clear_env (IdeSubprocessLauncher *self)
902 {
903 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
904
905 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), FALSE);
906
907 return priv->clear_env;
908 }
909
910 void
ide_subprocess_launcher_set_clear_env(IdeSubprocessLauncher * self,gboolean clear_env)911 ide_subprocess_launcher_set_clear_env (IdeSubprocessLauncher *self,
912 gboolean clear_env)
913 {
914 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
915
916 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
917
918 clear_env = !!clear_env;
919
920 if (priv->clear_env != clear_env)
921 {
922 priv->clear_env = clear_env;
923 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLEAR_ENV]);
924 }
925 }
926
927 void
ide_subprocess_launcher_take_stdin_fd(IdeSubprocessLauncher * self,gint stdin_fd)928 ide_subprocess_launcher_take_stdin_fd (IdeSubprocessLauncher *self,
929 gint stdin_fd)
930 {
931 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
932
933 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
934
935 if (priv->stdin_fd != stdin_fd)
936 {
937 if (priv->stdin_fd != -1)
938 close (priv->stdin_fd);
939 priv->stdin_fd = stdin_fd;
940 }
941 }
942
943 void
ide_subprocess_launcher_take_stdout_fd(IdeSubprocessLauncher * self,gint stdout_fd)944 ide_subprocess_launcher_take_stdout_fd (IdeSubprocessLauncher *self,
945 gint stdout_fd)
946 {
947 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
948
949 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
950
951 if (priv->stdout_fd != stdout_fd)
952 {
953 if (priv->stdout_fd != -1)
954 close (priv->stdout_fd);
955 priv->stdout_fd = stdout_fd;
956 }
957 }
958
959 void
ide_subprocess_launcher_take_stderr_fd(IdeSubprocessLauncher * self,gint stderr_fd)960 ide_subprocess_launcher_take_stderr_fd (IdeSubprocessLauncher *self,
961 gint stderr_fd)
962 {
963 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
964
965 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
966
967 if (priv->stderr_fd != stderr_fd)
968 {
969 if (priv->stderr_fd != -1)
970 close (priv->stderr_fd);
971 priv->stderr_fd = stderr_fd;
972 }
973 }
974
975 /**
976 * ide_subprocess_launcher_set_argv:
977 * @self: an #IdeSubprocessLauncher
978 * @args: (array zero-terminated=1) (element-type utf8) (transfer none): a
979 * %NULL terminated array of strings.
980 *
981 * Clears the previous arguments and copies @args as the new argument array for
982 * the subprocess.
983 */
984 void
ide_subprocess_launcher_set_argv(IdeSubprocessLauncher * self,const gchar * const * args)985 ide_subprocess_launcher_set_argv (IdeSubprocessLauncher *self,
986 const gchar * const *args)
987 {
988 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
989
990 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
991
992 g_ptr_array_remove_range (priv->argv, 0, priv->argv->len);
993
994 if (args != NULL)
995 {
996 for (guint i = 0; args[i] != NULL; i++)
997 g_ptr_array_add (priv->argv, g_strdup (args[i]));
998 }
999
1000 g_ptr_array_add (priv->argv, NULL);
1001 }
1002
1003 const gchar * const *
ide_subprocess_launcher_get_argv(IdeSubprocessLauncher * self)1004 ide_subprocess_launcher_get_argv (IdeSubprocessLauncher *self)
1005 {
1006 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
1007
1008 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
1009
1010 return (const gchar * const *)priv->argv->pdata;
1011 }
1012
1013 void
ide_subprocess_launcher_insert_argv(IdeSubprocessLauncher * self,guint index,const gchar * arg)1014 ide_subprocess_launcher_insert_argv (IdeSubprocessLauncher *self,
1015 guint index,
1016 const gchar *arg)
1017 {
1018 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
1019
1020 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
1021 g_return_if_fail (priv->argv->len > 0);
1022 g_return_if_fail (index < (priv->argv->len - 1));
1023 g_return_if_fail (arg != NULL);
1024
1025 g_ptr_array_insert (priv->argv, index, g_strdup (arg));
1026 }
1027
1028 void
ide_subprocess_launcher_replace_argv(IdeSubprocessLauncher * self,guint index,const gchar * arg)1029 ide_subprocess_launcher_replace_argv (IdeSubprocessLauncher *self,
1030 guint index,
1031 const gchar *arg)
1032 {
1033 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
1034 gchar *old_arg;
1035
1036 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
1037 g_return_if_fail (priv->argv->len > 0);
1038 g_return_if_fail (index < (priv->argv->len - 1));
1039 g_return_if_fail (arg != NULL);
1040
1041 /* overwriting in place */
1042 old_arg = g_ptr_array_index (priv->argv, index);
1043 g_ptr_array_index (priv->argv, index) = g_strdup (arg);
1044 g_free (old_arg);
1045 }
1046
1047 void
ide_subprocess_launcher_take_fd(IdeSubprocessLauncher * self,gint source_fd,gint dest_fd)1048 ide_subprocess_launcher_take_fd (IdeSubprocessLauncher *self,
1049 gint source_fd,
1050 gint dest_fd)
1051 {
1052 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
1053 FdMapping map = {
1054 .source_fd = source_fd,
1055 .dest_fd = dest_fd
1056 };
1057
1058 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
1059 g_return_if_fail (source_fd > -1);
1060 g_return_if_fail (dest_fd > -1);
1061
1062 if (priv->fd_mapping == NULL)
1063 priv->fd_mapping = g_array_new (FALSE, FALSE, sizeof (FdMapping));
1064
1065 g_array_append_val (priv->fd_mapping, map);
1066 }
1067
1068 void
ide_subprocess_launcher_set_stdout_file_path(IdeSubprocessLauncher * self,const gchar * stdout_file_path)1069 ide_subprocess_launcher_set_stdout_file_path (IdeSubprocessLauncher *self,
1070 const gchar *stdout_file_path)
1071 {
1072 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
1073
1074 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
1075
1076 if (g_strcmp0 (priv->stdout_file_path, stdout_file_path) != 0)
1077 {
1078 g_free (priv->stdout_file_path);
1079 priv->stdout_file_path = g_strdup (stdout_file_path);
1080 }
1081 }
1082
1083 void
ide_subprocess_launcher_prepend_path(IdeSubprocessLauncher * self,const gchar * path)1084 ide_subprocess_launcher_prepend_path (IdeSubprocessLauncher *self,
1085 const gchar *path)
1086 {
1087 const gchar *old_path;
1088
1089 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
1090
1091 if (path == NULL)
1092 return;
1093
1094 old_path = ide_subprocess_launcher_getenv (self, "PATH");
1095
1096 if (old_path != NULL)
1097 {
1098 g_autofree gchar *new_path = g_strdup_printf ("%s:%s", path, old_path);
1099 ide_subprocess_launcher_setenv (self, "PATH", new_path, TRUE);
1100 }
1101 else
1102 {
1103 ide_subprocess_launcher_setenv (self, "PATH", path, TRUE);
1104 }
1105 }
1106
1107 void
ide_subprocess_launcher_append_path(IdeSubprocessLauncher * self,const gchar * path)1108 ide_subprocess_launcher_append_path (IdeSubprocessLauncher *self,
1109 const gchar *path)
1110 {
1111 const gchar *old_path;
1112
1113 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
1114
1115 if (path == NULL)
1116 return;
1117
1118 old_path = ide_subprocess_launcher_getenv (self, "PATH");
1119
1120 if (old_path != NULL)
1121 {
1122 g_autofree gchar *new_path = g_strdup_printf ("%s:%s", old_path, path);
1123 ide_subprocess_launcher_setenv (self, "PATH", new_path, TRUE);
1124 }
1125 else
1126 {
1127 ide_subprocess_launcher_setenv (self, "PATH", path, TRUE);
1128 }
1129 }
1130
1131 gboolean
ide_subprocess_launcher_get_needs_tty(IdeSubprocessLauncher * self)1132 ide_subprocess_launcher_get_needs_tty (IdeSubprocessLauncher *self)
1133 {
1134 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
1135
1136 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), FALSE);
1137
1138 if ((priv->stdin_fd != -1 && isatty (priv->stdin_fd)) ||
1139 (priv->stdout_fd != -1 && isatty (priv->stdout_fd)) ||
1140 (priv->stderr_fd != -1 && isatty (priv->stderr_fd)))
1141 return TRUE;
1142
1143 if (priv->fd_mapping != NULL)
1144 {
1145 for (guint i = 0; i < priv->fd_mapping->len; i++)
1146 {
1147 const FdMapping *fdmap = &g_array_index (priv->fd_mapping, FdMapping, i);
1148
1149 switch (fdmap->dest_fd)
1150 {
1151 case STDIN_FILENO:
1152 case STDOUT_FILENO:
1153 case STDERR_FILENO:
1154 if (isatty (fdmap->source_fd))
1155 return TRUE;
1156 break;
1157
1158 default:
1159 break;
1160 }
1161 }
1162 }
1163
1164 return FALSE;
1165 }
1166
1167 /**
1168 * ide_subprocess_launcher_get_max_fd:
1169 * @self: a #IdeSubprocessLauncher
1170 *
1171 * Gets the hightest number of FD that has been mapped into the
1172 * subprocess launcher.
1173 *
1174 * This will always return a value >= 2 (to indicate stdin/stdout/stderr).
1175 *
1176 * Returns: an integer for the max-fd
1177 *
1178 * Since: 3.34
1179 */
1180 gint
ide_subprocess_launcher_get_max_fd(IdeSubprocessLauncher * self)1181 ide_subprocess_launcher_get_max_fd (IdeSubprocessLauncher *self)
1182 {
1183 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
1184 gint max_fd = 2;
1185
1186 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), 2);
1187
1188 if (priv->fd_mapping != NULL)
1189 {
1190 for (guint i = 0; i < priv->fd_mapping->len; i++)
1191 {
1192 const FdMapping *map = &g_array_index (priv->fd_mapping, FdMapping, i);
1193
1194 if (map->dest_fd > max_fd)
1195 max_fd = map->dest_fd;
1196 }
1197 }
1198
1199 return max_fd;
1200 }
1201
1202 const gchar *
ide_subprocess_launcher_get_arg(IdeSubprocessLauncher * self,guint pos)1203 ide_subprocess_launcher_get_arg (IdeSubprocessLauncher *self,
1204 guint pos)
1205 {
1206 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
1207
1208 g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
1209
1210 if (pos < priv->argv->len)
1211 return g_ptr_array_index (priv->argv, pos);
1212
1213 return NULL;
1214 }
1215
1216 void
ide_subprocess_launcher_join_args_for_sh_c(IdeSubprocessLauncher * self,guint start_pos)1217 ide_subprocess_launcher_join_args_for_sh_c (IdeSubprocessLauncher *self,
1218 guint start_pos)
1219 {
1220 IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
1221 const gchar * const *argv;
1222 GString *str;
1223
1224 g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
1225 g_return_if_fail (start_pos < priv->argv->len - 1);
1226
1227 str = g_string_new (NULL);
1228 argv = ide_subprocess_launcher_get_argv (self);
1229
1230 for (guint i = start_pos; argv[i] != NULL; i++)
1231 {
1232 g_autofree gchar *quoted_string = g_shell_quote (argv[i]);
1233
1234 if (i > 0)
1235 g_string_append_c (str, ' ');
1236 g_string_append (str, quoted_string);
1237 }
1238
1239 g_ptr_array_remove_range (priv->argv, start_pos, priv->argv->len - start_pos);
1240 g_ptr_array_add (priv->argv, g_string_free (g_steal_pointer (&str), FALSE));
1241 g_ptr_array_add (priv->argv, NULL);
1242 }
1243