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