1 /* ide-subprocess.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"
22
23 #include "config.h"
24
25 #include <string.h>
26 #include <libide-core.h>
27
28 #include "ide-subprocess.h"
29
G_DEFINE_INTERFACE(IdeSubprocess,ide_subprocess,G_TYPE_OBJECT)30 G_DEFINE_INTERFACE (IdeSubprocess, ide_subprocess, G_TYPE_OBJECT)
31
32 static void
33 ide_subprocess_default_init (IdeSubprocessInterface *iface)
34 {
35 }
36
37 #define WRAP_INTERFACE_METHOD(self, name, default_return, ...) \
38 ((IDE_SUBPROCESS_GET_IFACE(self)->name != NULL) ? \
39 IDE_SUBPROCESS_GET_IFACE(self)->name (self, ##__VA_ARGS__) : \
40 default_return)
41
42 const gchar *
ide_subprocess_get_identifier(IdeSubprocess * self)43 ide_subprocess_get_identifier (IdeSubprocess *self)
44 {
45 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
46
47 return WRAP_INTERFACE_METHOD (self, get_identifier, NULL);
48 }
49
50 /**
51 * ide_subprocess_get_stdout_pipe:
52 *
53 * Returns: (transfer none): a #GInputStream or %NULL.
54 *
55 * Since: 3.32
56 */
57 GInputStream *
ide_subprocess_get_stdout_pipe(IdeSubprocess * self)58 ide_subprocess_get_stdout_pipe (IdeSubprocess *self)
59 {
60 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
61
62 return WRAP_INTERFACE_METHOD (self, get_stdout_pipe, NULL);
63 }
64
65 /**
66 * ide_subprocess_get_stderr_pipe:
67 *
68 * Returns: (transfer none): a #GInputStream or %NULL.
69 *
70 * Since: 3.32
71 */
72 GInputStream *
ide_subprocess_get_stderr_pipe(IdeSubprocess * self)73 ide_subprocess_get_stderr_pipe (IdeSubprocess *self)
74 {
75 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
76
77 return WRAP_INTERFACE_METHOD (self, get_stderr_pipe, NULL);
78 }
79
80 /**
81 * ide_subprocess_get_stdin_pipe:
82 *
83 * Returns: (transfer none): a #GOutputStream or %NULL.
84 *
85 * Since: 3.32
86 */
87 GOutputStream *
ide_subprocess_get_stdin_pipe(IdeSubprocess * self)88 ide_subprocess_get_stdin_pipe (IdeSubprocess *self)
89 {
90 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
91
92 return WRAP_INTERFACE_METHOD (self, get_stdin_pipe, NULL);
93 }
94
95 gboolean
ide_subprocess_wait(IdeSubprocess * self,GCancellable * cancellable,GError ** error)96 ide_subprocess_wait (IdeSubprocess *self,
97 GCancellable *cancellable,
98 GError **error)
99 {
100 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
101 g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
102
103 return WRAP_INTERFACE_METHOD (self, wait, FALSE, cancellable, error);
104 }
105
106 gboolean
ide_subprocess_wait_check(IdeSubprocess * self,GCancellable * cancellable,GError ** error)107 ide_subprocess_wait_check (IdeSubprocess *self,
108 GCancellable *cancellable,
109 GError **error)
110 {
111 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
112 g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
113
114 return ide_subprocess_wait (self, cancellable, error) &&
115 ide_subprocess_check_exit_status (self, error);
116 }
117
118 void
ide_subprocess_wait_async(IdeSubprocess * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)119 ide_subprocess_wait_async (IdeSubprocess *self,
120 GCancellable *cancellable,
121 GAsyncReadyCallback callback,
122 gpointer user_data)
123 {
124 g_return_if_fail (IDE_IS_SUBPROCESS (self));
125 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
126
127 WRAP_INTERFACE_METHOD (self, wait_async, NULL, cancellable, callback, user_data);
128 }
129
130 gboolean
ide_subprocess_wait_finish(IdeSubprocess * self,GAsyncResult * result,GError ** error)131 ide_subprocess_wait_finish (IdeSubprocess *self,
132 GAsyncResult *result,
133 GError **error)
134 {
135 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
136
137 return WRAP_INTERFACE_METHOD (self, wait_finish, FALSE, result, error);
138 }
139
140 static void
ide_subprocess_wait_check_cb(GObject * object,GAsyncResult * result,gpointer user_data)141 ide_subprocess_wait_check_cb (GObject *object,
142 GAsyncResult *result,
143 gpointer user_data)
144 {
145 IdeSubprocess *self = (IdeSubprocess *)object;
146 g_autoptr(GTask) task = user_data;
147 g_autoptr(GError) error = NULL;
148
149 IDE_ENTRY;
150
151 g_assert (IDE_IS_SUBPROCESS (self));
152 g_assert (G_IS_TASK (task));
153
154 if (!ide_subprocess_wait_finish (self, result, &error))
155 {
156 g_task_return_error (task, g_steal_pointer (&error));
157 IDE_EXIT;
158 }
159
160 if (ide_subprocess_get_if_signaled (self))
161 {
162 gint term_sig = ide_subprocess_get_term_sig (self);
163
164 g_task_return_new_error (task,
165 G_SPAWN_ERROR,
166 G_SPAWN_ERROR_FAILED,
167 "Child process killed by signal %d",
168 term_sig);
169 IDE_EXIT;
170 }
171
172 if (!ide_subprocess_check_exit_status (self, &error))
173 {
174 g_task_return_error (task, g_steal_pointer (&error));
175 IDE_EXIT;
176 }
177
178 g_task_return_boolean (task, TRUE);
179
180 IDE_EXIT;
181 }
182
183 void
ide_subprocess_wait_check_async(IdeSubprocess * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)184 ide_subprocess_wait_check_async (IdeSubprocess *self,
185 GCancellable *cancellable,
186 GAsyncReadyCallback callback,
187 gpointer user_data)
188 {
189 g_autoptr(GTask) task = NULL;
190
191 IDE_ENTRY;
192
193 g_return_if_fail (IDE_IS_SUBPROCESS (self));
194 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
195
196 task = g_task_new (self, cancellable, callback, user_data);
197 g_task_set_source_tag (task, ide_subprocess_wait_check_async);
198
199 ide_subprocess_wait_async (self,
200 cancellable,
201 ide_subprocess_wait_check_cb,
202 g_steal_pointer (&task));
203
204 IDE_EXIT;
205 }
206
207 gboolean
ide_subprocess_wait_check_finish(IdeSubprocess * self,GAsyncResult * result,GError ** error)208 ide_subprocess_wait_check_finish (IdeSubprocess *self,
209 GAsyncResult *result,
210 GError **error)
211 {
212 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
213 g_return_val_if_fail (G_IS_TASK (result), FALSE);
214 g_return_val_if_fail (g_task_is_valid (G_TASK (result), self), FALSE);
215
216 return g_task_propagate_boolean (G_TASK (result), error);
217 }
218
219 gboolean
ide_subprocess_get_successful(IdeSubprocess * self)220 ide_subprocess_get_successful (IdeSubprocess *self)
221 {
222 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
223
224 return WRAP_INTERFACE_METHOD (self, get_successful, FALSE);
225 }
226
227 gboolean
ide_subprocess_get_if_exited(IdeSubprocess * self)228 ide_subprocess_get_if_exited (IdeSubprocess *self)
229 {
230 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
231
232 return WRAP_INTERFACE_METHOD (self, get_if_exited, FALSE);
233 }
234
235 gint
ide_subprocess_get_exit_status(IdeSubprocess * self)236 ide_subprocess_get_exit_status (IdeSubprocess *self)
237 {
238 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), 0);
239
240 return WRAP_INTERFACE_METHOD (self, get_exit_status, 0);
241 }
242
243 gboolean
ide_subprocess_get_if_signaled(IdeSubprocess * self)244 ide_subprocess_get_if_signaled (IdeSubprocess *self)
245 {
246 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
247
248 return WRAP_INTERFACE_METHOD (self, get_if_signaled, FALSE);
249 }
250
251 gint
ide_subprocess_get_term_sig(IdeSubprocess * self)252 ide_subprocess_get_term_sig (IdeSubprocess *self)
253 {
254 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), 0);
255
256 return WRAP_INTERFACE_METHOD (self, get_term_sig, 0);
257 }
258
259 gint
ide_subprocess_get_status(IdeSubprocess * self)260 ide_subprocess_get_status (IdeSubprocess *self)
261 {
262 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), 0);
263
264 return WRAP_INTERFACE_METHOD (self, get_status, 0);
265 }
266
267 void
ide_subprocess_send_signal(IdeSubprocess * self,gint signal_num)268 ide_subprocess_send_signal (IdeSubprocess *self,
269 gint signal_num)
270 {
271 g_return_if_fail (IDE_IS_SUBPROCESS (self));
272
273 WRAP_INTERFACE_METHOD (self, send_signal, NULL, signal_num);
274 }
275
276 void
ide_subprocess_force_exit(IdeSubprocess * self)277 ide_subprocess_force_exit (IdeSubprocess *self)
278 {
279 g_return_if_fail (IDE_IS_SUBPROCESS (self));
280
281 WRAP_INTERFACE_METHOD (self, force_exit, NULL);
282 }
283
284 gboolean
ide_subprocess_communicate(IdeSubprocess * self,GBytes * stdin_buf,GCancellable * cancellable,GBytes ** stdout_buf,GBytes ** stderr_buf,GError ** error)285 ide_subprocess_communicate (IdeSubprocess *self,
286 GBytes *stdin_buf,
287 GCancellable *cancellable,
288 GBytes **stdout_buf,
289 GBytes **stderr_buf,
290 GError **error)
291 {
292 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
293 g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
294
295 return WRAP_INTERFACE_METHOD (self, communicate, FALSE, stdin_buf, cancellable, stdout_buf, stderr_buf, error);
296 }
297
298 /**
299 * ide_subprocess_communicate_utf8:
300 * @self: an #IdeSubprocess
301 * @stdin_buf: (nullable): input to deliver to the subprocesses stdin stream
302 * @cancellable: (nullable): an optional #GCancellable
303 * @stdout_buf: (out) (nullable): an optional location for the stdout contents
304 * @stderr_buf: (out) (nullable): an optional location for the stderr contents
305 *
306 * This process acts identical to g_subprocess_communicate_utf8().
307 *
308 * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
309 *
310 * Since: 3.32
311 */
312 gboolean
ide_subprocess_communicate_utf8(IdeSubprocess * self,const gchar * stdin_buf,GCancellable * cancellable,gchar ** stdout_buf,gchar ** stderr_buf,GError ** error)313 ide_subprocess_communicate_utf8 (IdeSubprocess *self,
314 const gchar *stdin_buf,
315 GCancellable *cancellable,
316 gchar **stdout_buf,
317 gchar **stderr_buf,
318 GError **error)
319 {
320 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
321 g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
322
323 return WRAP_INTERFACE_METHOD (self, communicate_utf8, FALSE, stdin_buf, cancellable, stdout_buf, stderr_buf, error);
324 }
325
326 /**
327 * ide_subprocess_communicate_async:
328 * @self: An #IdeSubprocess
329 * @stdin_buf: (nullable): a #GBytes to send to stdin or %NULL
330 * @cancellable: (nullable): a #GCancellable or %NULL
331 * @callback: A callback to complete the request
332 * @user_data: user data for @callback
333 *
334 * Asynchronously communicates with the the child process.
335 *
336 * There is no need to call ide_subprocess_wait() on the process if using
337 * this asynchronous operation as it will internally wait for the child
338 * to exit or be signaled.
339 *
340 * Ensure you've set the proper flags to ensure that you can write to stdin
341 * or read from stderr/stdout as necessary.
342 *
343 * Since: 3.32
344 */
345 void
ide_subprocess_communicate_async(IdeSubprocess * self,GBytes * stdin_buf,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)346 ide_subprocess_communicate_async (IdeSubprocess *self,
347 GBytes *stdin_buf,
348 GCancellable *cancellable,
349 GAsyncReadyCallback callback,
350 gpointer user_data)
351 {
352 g_return_if_fail (IDE_IS_SUBPROCESS (self));
353 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
354
355 WRAP_INTERFACE_METHOD (self, communicate_async, NULL, stdin_buf, cancellable, callback, user_data);
356 }
357
358 /**
359 * ide_subprocess_communicate_finish:
360 * @self: An #IdeSubprocess
361 * @result: a #GAsyncResult
362 * @stdout_buf: (out) (optional): A location for a #Bytes.
363 * @stderr_buf: (out) (optional): A location for a #Bytes.
364 * @error: a location for a #GError
365 *
366 * Finishes a request to ide_subprocess_communicate_async().
367 *
368 * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
369 *
370 * Since: 3.32
371 */
372 gboolean
ide_subprocess_communicate_finish(IdeSubprocess * self,GAsyncResult * result,GBytes ** stdout_buf,GBytes ** stderr_buf,GError ** error)373 ide_subprocess_communicate_finish (IdeSubprocess *self,
374 GAsyncResult *result,
375 GBytes **stdout_buf,
376 GBytes **stderr_buf,
377 GError **error)
378 {
379 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
380 g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
381
382 return WRAP_INTERFACE_METHOD (self, communicate_finish, FALSE, result, stdout_buf, stderr_buf, error);
383 }
384
385 /**
386 * ide_subprocess_communicate_utf8_async:
387 * @stdin_buf: (nullable): The data to send to stdin or %NULL
388 *
389 *
390 * Since: 3.32
391 */
392 void
ide_subprocess_communicate_utf8_async(IdeSubprocess * self,const gchar * stdin_buf,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)393 ide_subprocess_communicate_utf8_async (IdeSubprocess *self,
394 const gchar *stdin_buf,
395 GCancellable *cancellable,
396 GAsyncReadyCallback callback,
397 gpointer user_data)
398 {
399 g_return_if_fail (IDE_IS_SUBPROCESS (self));
400 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
401
402 WRAP_INTERFACE_METHOD (self, communicate_utf8_async, NULL, stdin_buf, cancellable, callback, user_data);
403 }
404
405 /**
406 * ide_subprocess_communicate_utf8_finish:
407 * @self: An #IdeSubprocess
408 * @result: a #GAsyncResult
409 * @stdout_buf: (out) (optional): A location for the UTF-8 formatted output string or %NULL
410 * @stderr_buf: (out) (optional): A location for the UTF-8 formatted output string or %NULL
411 * @error: A location for a #GError, or %NULL
412 *
413 * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
414 *
415 * Since: 3.32
416 */
417 gboolean
ide_subprocess_communicate_utf8_finish(IdeSubprocess * self,GAsyncResult * result,gchar ** stdout_buf,gchar ** stderr_buf,GError ** error)418 ide_subprocess_communicate_utf8_finish (IdeSubprocess *self,
419 GAsyncResult *result,
420 gchar **stdout_buf,
421 gchar **stderr_buf,
422 GError **error)
423 {
424 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
425 g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
426
427 return WRAP_INTERFACE_METHOD (self, communicate_utf8_finish, FALSE, result, stdout_buf, stderr_buf, error);
428 }
429
430 gboolean
ide_subprocess_check_exit_status(IdeSubprocess * self,GError ** error)431 ide_subprocess_check_exit_status (IdeSubprocess *self,
432 GError **error)
433 {
434 gint exit_status;
435
436 g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
437
438 exit_status = ide_subprocess_get_exit_status (self);
439
440 return g_spawn_check_wait_status (exit_status, error);
441 }
442