1    	/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2    	 *
3    	 * Copyright (C) 2012 Colin Walters <walters@verbum.org>
4    	 *
5    	 * This library is free software; you can redistribute it and/or
6    	 * modify it under the terms of the GNU Lesser General Public
7    	 * License as published by the Free Software Foundation; either
8    	 * version 2 of the License, or (at your option) any later version.
9    	 *
10   	 * This library 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 GNU
13   	 * Lesser General Public License for more details.
14   	 *
15   	 * You should have received a copy of the GNU Lesser General Public
16   	 * License along with this library; if not, write to the
17   	 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18   	 * Boston, MA 02111-1307, USA.
19   	 */
20   	
21   	#include "config.h"
22   	
23   	#include "libgsystem.h"
24   	
25   	#if GLIB_CHECK_VERSION(2,34,0)
26   	
27   	#ifdef G_OS_UNIX
28   	#include <gio/gunixoutputstream.h>
29   	#include <gio/gfiledescriptorbased.h>
30   	#include <gio/gunixinputstream.h>
31   	#include <glib-unix.h>
32   	#endif
33   	
34   	/**
35   	 * SECTION:gssubprocesscontext
36   	 * @title: GSSubprocess Context
37   	 * @short_description: Environment options for launching a child process
38   	 *
39   	 * This class contains a set of options for launching child processes,
40   	 * such as where its standard input and output will be directed, the
41   	 * argument list, the environment, and more.
42   	 *
43   	 * While the #GSSubprocess class has high level functions covering
44   	 * popular cases, use of this class allows access to more advanced
45   	 * options.  It can also be used to launch multiple subprocesses with
46   	 * a similar configuration.
47   	 *
48   	 * Since: 2.36
49   	 */
50   	
51   	#include "config.h"
52   	
53   	#include "gsystem-subprocess-context-private.h"
54   	#include "gsystem-subprocess.h"
55   	
56   	#include <string.h>
57   	
58   	typedef GObjectClass GSSubprocessContextClass;
59   	
60   	G_DEFINE_TYPE (GSSubprocessContext, gs_subprocess_context, G_TYPE_OBJECT);
61   	
62   	enum
63   	{
64   	  PROP_0,
65   	  PROP_ARGV,
66   	  N_PROPS
67   	};
68   	
69   	static GParamSpec *gs_subprocess_context_pspecs[N_PROPS];
70   	
71   	/**
72   	 * gs_subprocess_context_new:
73   	 * @argv: Argument list
74   	 *
75   	 * Returns: (transfer full): A new instance of a #GSSubprocessContext.
76   	 */
77   	GSSubprocessContext *
78   	gs_subprocess_context_new (gchar           **argv)
79   	{
80   	  g_return_val_if_fail (argv != NULL && argv[0] != NULL, NULL);
81   	
82   	  return g_object_new (GS_TYPE_SUBPROCESS_CONTEXT,
83   			       "argv", argv,
84   			       NULL);
85   	}
86   	
87   	GSSubprocessContext *
88   	gs_subprocess_context_newv (const gchar  *first_arg,
89   	                           ...)
90   	{
91   	  GSSubprocessContext *result;
92   	  va_list args;
93   	
94   	  g_return_val_if_fail (first_arg != NULL, NULL);
95   	
96   	  va_start (args, first_arg);
97   	  result = gs_subprocess_context_newa (first_arg, args);
98   	  va_end (args);
99   	  
100  	  return result;
101  	}
102  	
103  	/**
104  	 * gs_subprocess_context_newa:
105  	 * @first_arg: First argument
106  	 * @args: a va_list
107  	 *
108  	 * Returns: (transfer full): A new instance of a #GSSubprocessContext.
109  	 */
110  	GSSubprocessContext *
111  	gs_subprocess_context_newa (const gchar *first_arg,
112  	                           va_list      args)
113  	{
114  	  GSSubprocessContext *result;
115  	  GPtrArray *argv;
116  	
117  	  g_return_val_if_fail (first_arg != NULL, NULL);
118  	
119  	  argv = g_ptr_array_new ();
120  	  do
121  	    g_ptr_array_add (argv, (gchar*)first_arg);
122  	  while ((first_arg = va_arg (args, const gchar *)) != NULL);
123  	  g_ptr_array_add (argv, NULL);
124  	
125  	  result = gs_subprocess_context_new ((gchar**)argv->pdata);
126  	  
127  	  return result;
128  	}
129  	
130  	#ifdef G_OS_UNIX
131  	GSSubprocessContext *
132  	gs_subprocess_context_new_argv0 (const gchar      *argv0,
133  	                                gchar           **argv)
134  	{
135  	  GSSubprocessContext *result;
136  	  GPtrArray *real_argv;
137  	  gchar **iter;
138  	  
139  	  g_return_val_if_fail (argv0 != NULL, NULL);
140  	  g_return_val_if_fail (argv != NULL && argv[0] != NULL, NULL);
141  	  
142  	  real_argv = g_ptr_array_new ();
143  	  g_ptr_array_add (real_argv, (gchar*)argv0);
144  	  for (iter = argv; *iter; iter++)
145  	    g_ptr_array_add (real_argv, (gchar*) *iter);
146  	  g_ptr_array_add (real_argv, NULL);
147  	
148  	  result = g_object_new (GS_TYPE_SUBPROCESS_CONTEXT,
149  	                         "argv", real_argv->pdata,
150  	                         NULL);
151  	  result->has_argv0 = TRUE;
152  	
153  	  return result;
154  	}
155  	#endif
156  	
157  	static void
158  	gs_subprocess_context_init (GSSubprocessContext  *self)
159  	{
160  	  self->stdin_fd = -1;
161  	  self->stdout_fd = -1;
162  	  self->stderr_fd = -1;
163  	  self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT;
164  	  self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT;
165  	  self->postfork_close_fds = g_array_new (FALSE, FALSE, sizeof (int));
166  	  self->inherit_fds = g_array_new (FALSE, FALSE, sizeof (int));
167  	}
168  	
169  	static void
170  	gs_subprocess_context_finalize (GObject *object)
171  	{
172  	  GSSubprocessContext *self = GS_SUBPROCESS_CONTEXT (object);
173  	
174  	  g_strfreev (self->argv);
175  	  g_strfreev (self->envp);
176  	  g_free (self->cwd);
177  	
178  	  g_free (self->stdin_path);
179  	  g_free (self->stdout_path);
180  	  g_free (self->stderr_path);
181  	
182  	  g_array_unref (self->postfork_close_fds);
183  	  g_array_unref (self->inherit_fds);
184  	
185  	  if (G_OBJECT_CLASS (gs_subprocess_context_parent_class)->finalize != NULL)
186  	    G_OBJECT_CLASS (gs_subprocess_context_parent_class)->finalize (object);
187  	}
188  	
189  	static void
190  	gs_subprocess_context_set_property (GObject      *object,
191  					   guint         prop_id,
192  					   const GValue *value,
193  					   GParamSpec   *pspec)
194  	{
195  	  GSSubprocessContext *self = GS_SUBPROCESS_CONTEXT (object);
196  	
197  	  switch (prop_id)
198  	    {
199  	    case PROP_ARGV:
200  	      self->argv = (gchar**) g_value_dup_boxed (value);
201  	      break;
202  	
203  	    default:
204  	      g_assert_not_reached ();
205  	    }
206  	}
207  	
208  	static void
209  	gs_subprocess_context_get_property (GObject    *object,
210  					   guint       prop_id,
211  					   GValue     *value,
212  					   GParamSpec *pspec)
213  	{
214  	  GSSubprocessContext *self = GS_SUBPROCESS_CONTEXT (object);
215  	
216  	  switch (prop_id)
217  	    {
218  	    case PROP_ARGV:
219  	      g_value_set_boxed (value, self->argv);
220  	      break;
221  	
222  	    default:
223  	      g_assert_not_reached ();
224  	    }
225  	}
226  	
227  	static void
228  	gs_subprocess_context_class_init (GSSubprocessContextClass *class)
229  	{
230  	  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
231  	
232  	  gobject_class->finalize = gs_subprocess_context_finalize;
233  	  gobject_class->get_property = gs_subprocess_context_get_property;
234  	  gobject_class->set_property = gs_subprocess_context_set_property;
235  	
236  	  /**
237  	   * GSSubprocessContext:argv:
238  	   *
239  	   * Array of arguments passed to child process; must have at least
240  	   * one element.  The first element has special handling - if it is
241  	   * an not absolute path ( as determined by g_path_is_absolute() ),
242  	   * then the system search path will be used.  See
243  	   * %G_SPAWN_SEARCH_PATH.
244  	   * 
245  	   * Note that in order to use the Unix-specific argv0 functionality,
246  	   * you must use the setter function
247  	   * gs_subprocess_context_set_args_and_argv0().  For more information
248  	   * about this, see %G_SPAWN_FILE_AND_ARGV_ZERO.
249  	   *
250  	   * Since: 2.36
251  	   */
252  	  gs_subprocess_context_pspecs[PROP_ARGV] = g_param_spec_boxed ("argv", "Arguments", "Arguments for child process", G_TYPE_STRV,
253  								       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
254  	
255  	  g_object_class_install_properties (gobject_class, N_PROPS, gs_subprocess_context_pspecs);
256  	}
257  	
258  	/**
259  	 * gs_subprocess_context_argv_append:
260  	 * @self:
261  	 * @arg: An argument
262  	 *
263  	 * Append an argument to the child's argument vector.
264  	 */
265  	void
266  	gs_subprocess_context_argv_append (GSSubprocessContext  *self,
267  	                                   const gchar          *arg)
268  	{
269  	  GPtrArray *new_argv = g_ptr_array_new ();
270  	  gchar **iter;
271  	
272  	  for (iter = self->argv; *iter; iter++)
273  	    g_ptr_array_add (new_argv, *iter);
274  	  g_ptr_array_add (new_argv, g_strdup (arg));
275  	  g_ptr_array_add (new_argv, NULL);
276  	
277  	  /* Don't free elements */
278  	  g_free (self->argv);
279  	  self->argv = (char**)g_ptr_array_free (new_argv, FALSE);
280  	}
281  	
282  	/* Environment */
283  	
284  	/**
285  	 * gs_subprocess_context_set_environment:
286  	 * @self:
287  	 * @environ: (array zero-terminated=1) (element-type utf8): Environment KEY=VALUE pairs
288  	 *
289  	 * Replace the environment that will be used for the child process.
290  	 * The default is to inherit the current process.
291  	 */
292  	void
293  	gs_subprocess_context_set_environment (GSSubprocessContext           *self,
294  	                                       gchar                        **env)
295  	{
296  	  g_strfreev (self->envp);
297  	  self->envp = g_strdupv (env);
298  	}
299  	
300  	void
301  	gs_subprocess_context_set_cwd (GSSubprocessContext           *self,
302  				      const gchar                  *cwd)
303  	{
304  	  g_free (self->cwd);
305  	  self->cwd = g_strdup (cwd);
306  	}
307  	
308  	void
309  	gs_subprocess_context_set_keep_descriptors (GSSubprocessContext           *self,
310  						   gboolean                      keep_descriptors)
311  	
312  	{
313  	  self->keep_descriptors = keep_descriptors ? 1 : 0;
314  	}
315  	
316  	void
317  	gs_subprocess_context_set_search_path (GSSubprocessContext           *self,
318  					      gboolean                      search_path,
319  					      gboolean                      search_path_from_envp)
320  	{
321  	  self->search_path = search_path ? 1 : 0;
322  	  self->search_path_from_envp = search_path_from_envp ? 1 : 0;
323  	}
324  	
325  	void
326  	gs_subprocess_context_set_stdin_disposition (GSSubprocessContext           *self,
327  						    GSSubprocessStreamDisposition  disposition)
328  	{
(1) Event cond_true: Condition "disposition != GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE", taking true branch
(2) Event if_fallthrough: Falling through to end of if statement
(3) Event if_end: End of if statement
(4) Event cond_true: Condition "({...})", taking true branch
(5) Event if_fallthrough: Falling through to end of if statement
(6) Event if_end: End of if statement
329  	  g_return_if_fail (disposition != GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE);
(7) Event deref_parm: Directly dereferencing parameter "self".
330  	  self->stdin_disposition = disposition;
331  	}
332  	
333  	void
334  	gs_subprocess_context_set_stdout_disposition (GSSubprocessContext           *self,
335  						     GSSubprocessStreamDisposition  disposition)
336  	{
337  	  g_return_if_fail (disposition != GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE);
338  	  self->stdout_disposition = disposition;
339  	}
340  	
341  	void
342  	gs_subprocess_context_set_stderr_disposition (GSSubprocessContext           *self,
343  						     GSSubprocessStreamDisposition  disposition)
344  	{
345  	  self->stderr_disposition = disposition;
346  	}
347  	
348  	#ifdef G_OS_UNIX
349  	void
350  	gs_subprocess_context_set_stdin_file_path (GSSubprocessContext           *self,
351  						  const gchar                  *path)
352  	{
353  	  self->stdin_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
354  	  g_free (self->stdin_path);
355  	  self->stdin_path = g_strdup (path);
356  	}
357  	
358  	void
359  	gs_subprocess_context_set_stdin_fd        (GSSubprocessContext           *self,
360  						  gint                          fd)
361  	{
362  	  self->stdin_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
363  	  self->stdin_fd = fd;
364  	}
365  	
366  	void
367  	gs_subprocess_context_set_stdout_file_path (GSSubprocessContext           *self,
368  						   const gchar                  *path)
369  	{
370  	  self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
371  	  g_free (self->stdout_path);
372  	  self->stdout_path = g_strdup (path);
373  	}
374  	
375  	void
376  	gs_subprocess_context_set_stdout_fd (GSSubprocessContext           *self,
377  					    gint                          fd)
378  	{
379  	  self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
380  	  self->stdout_fd = fd;
381  	}
382  	
383  	void
384  	gs_subprocess_context_set_stderr_file_path (GSSubprocessContext           *self,
385  						   const gchar                  *path)
386  	{
387  	  self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
388  	  g_free (self->stderr_path);
389  	  self->stderr_path = g_strdup (path);
390  	}
391  	
392  	void
393  	gs_subprocess_context_set_stderr_fd        (GSSubprocessContext           *self,
394  						   gint                          fd)
395  	{
396  	  self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
397  	  self->stderr_fd = fd;
398  	}
399  	#endif
400  	
401  	#ifdef G_OS_UNIX
402  	/**
403  	 * gs_subprocess_context_set_child_setup: (skip)
404  	 * @self:
405  	 * @child_setup: Function to call in the newly forked child, before execve()
406  	 * @user_data: Data passed to child
407  	 *
408  	 * FIXME - note extensive restricitons on GSpawnChildSetupFunc here
409  	 */
410  	void
411  	gs_subprocess_context_set_child_setup (GSSubprocessContext           *self,
412  					      GSpawnChildSetupFunc          child_setup,
413  					      gpointer                      user_data)
414  	{
415  	  self->child_setup_func = child_setup;
416  	  self->child_setup_data = user_data;
417  	}
418  	
419  	static gboolean
420  	open_pipe_internal (GSSubprocessContext         *self,
421  	                    gboolean                     for_read,
422  	                    void                       **out_stream,
423  	                    gint                        *out_fdno,
424  	                    GError                     **error)
425  	{
426  	  int pipefds[2];
427  	
428  	  g_return_val_if_fail (out_stream != NULL, FALSE);
429  	  g_return_val_if_fail (out_fdno != NULL, FALSE);
430  	
431  	  if (!g_unix_open_pipe (pipefds, FD_CLOEXEC, error))
432  	    return FALSE;
433  	
434  	  if (for_read)
435  	    {
436  	      *out_stream = g_unix_input_stream_new (pipefds[0], TRUE);
437  	      *out_fdno = pipefds[1];
438  	    }
439  	  else
440  	    {
441  	      *out_stream = g_unix_output_stream_new (pipefds[1], TRUE);
442  	      *out_fdno = pipefds[0];
443  	    }
444  	  g_array_append_val (self->inherit_fds, *out_fdno);
445  	  g_array_append_val (self->postfork_close_fds, *out_fdno);
446  	
447  	  return TRUE;
448  	}
449  	
450  	/**
451  	 * gs_subprocess_context_open_pipe_read:
452  	 * @self:
453  	 * @out_stream: (out) (transfer full): A newly referenced output stream
454  	 * @out_fdno: (out): File descriptor number for the subprocess side of the pipe
455  	 *
456  	 * This allows you to open a pipe between the parent and child
457  	 * processes, independent of the standard streams.  For this function,
458  	 * the pipe is set up so that the parent can read, and the child can
459  	 * write.  For the opposite version, see
460  	 * gs_subprocess_context_open_pipe_write().
461  	 *
462  	 * The returned @out_fdno is the file descriptor number that the child
463  	 * will see; you need to communicate this number via a separate
464  	 * channel, such as the argument list.  For example, if you're using
465  	 * this pipe to send a password, provide
466  	 * <literal>--password-fd=&lt;fdno string&gt;</literal>.
467  	 *
468  	 * Returns: %TRUE on success, %FALSE on error (and @error will be set)
469  	 */
470  	gboolean
471  	gs_subprocess_context_open_pipe_read (GSSubprocessContext         *self,
472  	                                      GInputStream               **out_stream,
473  	                                      gint                        *out_fdno,
474  	                                      GError                     **error)
475  	{
476  	  return open_pipe_internal (self, TRUE, (void**)out_stream, out_fdno, error);
477  	}
478  	
479  	/**
480  	 * gs_subprocess_context_open_pipe_write:
481  	 * @self:
482  	 * @out_stream: (out) (transfer full): A newly referenced stream
483  	 * @out_fdno: (out): File descriptor number for the subprocess side of the pipe
484  	 *
485  	 * Like gs_subprocess_context_open_pipe_read(), but returns a writable
486  	 * channel from which the child process can read.
487  	 *
488  	 * Returns: %TRUE on success, %FALSE on error (and @error will be set)
489  	 */
490  	gboolean
491  	gs_subprocess_context_open_pipe_write (GSSubprocessContext         *self,
492  	                                       GOutputStream              **out_stream,
493  	                                       gint                        *out_fdno,
494  	                                       GError                     **error)
495  	{
496  	  return open_pipe_internal (self, FALSE, (void**)out_stream, out_fdno, error);
497  	}
498  	
499  	#endif
500  	
501  	#endif
502