1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2007-2012 Richard Hughes <richard@hughsie.com>
4 *
5 * Licensed under the GNU General Public License Version 2
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include "config.h"
23
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <time.h>
27 #include <errno.h>
28 #include <signal.h>
29
30 #include <string.h>
31 #include <sys/time.h>
32 #include <sys/types.h>
33 #include <sys/resource.h>
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif /* HAVE_UNISTD_H */
37
38 #include <sys/wait.h>
39 #include <fcntl.h>
40
41 #include <glib/gi18n.h>
42
43 #include "cd-spawn.h"
44
45 static void cd_spawn_finalize (GObject *object);
46
47 #define CD_SPAWN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CD_TYPE_SPAWN, CdSpawnPrivate))
48 #define CD_SPAWN_POLL_DELAY 50 /* ms */
49 #define CD_SPAWN_SIGKILL_DELAY 2500 /* ms */
50
51 struct CdSpawnPrivate
52 {
53 pid_t child_pid;
54 gint stdin_fd;
55 gint stdout_fd;
56 gint stderr_fd;
57 guint poll_id;
58 guint kill_id;
59 gboolean finished;
60 gboolean allow_sigkill;
61 CdSpawnExitType exit;
62 GString *stdout_buf;
63 GString *stderr_buf;
64 };
65
66 enum {
67 SIGNAL_EXIT,
68 SIGNAL_STDOUT,
69 SIGNAL_STDERR,
70 SIGNAL_LAST
71 };
72
73 static guint signals [SIGNAL_LAST] = { 0 };
74
G_DEFINE_TYPE(CdSpawn,cd_spawn,G_TYPE_OBJECT)75 G_DEFINE_TYPE (CdSpawn, cd_spawn, G_TYPE_OBJECT)
76
77 /**
78 * cd_spawn_read_fd_into_buffer:
79 **/
80 static gboolean
81 cd_spawn_read_fd_into_buffer (gint fd, GString *string)
82 {
83 gint bytes_read;
84 gchar buffer[BUFSIZ];
85
86 /* ITS4: ignore, we manually NULL terminate and GString cannot overflow */
87 while ((bytes_read = read (fd, buffer, BUFSIZ-1)) > 0) {
88 buffer[bytes_read] = '\0';
89 g_string_append (string, buffer);
90 }
91
92 return TRUE;
93 }
94
95 /**
96 * cd_spawn_emit_whole_lines:
97 **/
98 static gboolean
cd_spawn_emit_whole_lines(CdSpawn * spawn,GString * string)99 cd_spawn_emit_whole_lines (CdSpawn *spawn, GString *string)
100 {
101 guint i;
102 guint size;
103 guint bytes_processed;
104 g_auto(GStrv) lines = NULL;
105
106 /* if nothing then don't emit */
107 if (string->len == 0)
108 return FALSE;
109
110 /* split into lines - the last line may be incomplete */
111 lines = g_strsplit (string->str, "\n", 0);
112 if (lines == NULL)
113 return FALSE;
114
115 /* find size */
116 size = g_strv_length (lines);
117
118 bytes_processed = 0;
119 /* we only emit n-1 strings */
120 for (i = 0; i < (size-1); i++) {
121 g_signal_emit (spawn, signals [SIGNAL_STDOUT], 0, lines[i]);
122 /* ITS4: ignore, g_strsplit always NULL terminates */
123 bytes_processed += strlen (lines[i]) + 1;
124 }
125
126 /* remove the text we've processed */
127 g_string_erase (string, 0, bytes_processed);
128 return TRUE;
129 }
130
131 /**
132 * cd_spawn_exit_type_enum_to_string:
133 **/
134 static const gchar *
cd_spawn_exit_type_enum_to_string(CdSpawnExitType type)135 cd_spawn_exit_type_enum_to_string (CdSpawnExitType type)
136 {
137 if (type == CD_SPAWN_EXIT_TYPE_SUCCESS)
138 return "success";
139 if (type == CD_SPAWN_EXIT_TYPE_FAILED)
140 return "failed";
141 if (type == CD_SPAWN_EXIT_TYPE_SIGQUIT)
142 return "sigquit";
143 if (type == CD_SPAWN_EXIT_TYPE_SIGKILL)
144 return "sigkill";
145 return "unknown";
146 }
147
148 /**
149 * cd_spawn_check_child:
150 **/
151 static gboolean
cd_spawn_check_child(CdSpawn * spawn)152 cd_spawn_check_child (CdSpawn *spawn)
153 {
154 pid_t pid;
155 int status;
156 gint retval;
157 static guint limit_printing = 0;
158
159 /* this shouldn't happen */
160 if (spawn->priv->finished) {
161 g_warning ("finished twice!");
162 return G_SOURCE_REMOVE;
163 }
164
165 cd_spawn_read_fd_into_buffer (spawn->priv->stdout_fd, spawn->priv->stdout_buf);
166 cd_spawn_read_fd_into_buffer (spawn->priv->stderr_fd, spawn->priv->stderr_buf);
167
168 /* emit all lines on standard out in one callback, as it's all probably
169 * related to the error that just happened */
170 if (spawn->priv->stderr_buf->len != 0) {
171 g_signal_emit (spawn, signals [SIGNAL_STDERR], 0, spawn->priv->stderr_buf->str);
172 g_string_set_size (spawn->priv->stderr_buf, 0);
173 }
174
175 /* all usual output goes on standard out, only bad libraries bitch to stderr */
176 cd_spawn_emit_whole_lines (spawn, spawn->priv->stdout_buf);
177
178 /* Only print one in twenty times to avoid filling the screen */
179 if (limit_printing++ % 20 == 0)
180 g_debug ("polling child_pid=%ld (1/20)", (long)spawn->priv->child_pid);
181
182 /* check if the child exited */
183 pid = waitpid (spawn->priv->child_pid, &status, WNOHANG);
184 if (pid == -1) {
185 g_warning ("failed to get the child PID data for %ld", (long)spawn->priv->child_pid);
186 return G_SOURCE_CONTINUE;
187 }
188 if (pid == 0) {
189 /* process still exist, but has not changed state */
190 return G_SOURCE_CONTINUE;
191 }
192 if (pid != spawn->priv->child_pid) {
193 g_warning ("some other process id was returned: got %ld and wanted %ld",
194 (long)pid, (long)spawn->priv->child_pid);
195 return G_SOURCE_CONTINUE;
196 }
197
198 /* disconnect the poll as there will be no more updates */
199 if (spawn->priv->poll_id > 0) {
200 g_source_remove (spawn->priv->poll_id);
201 spawn->priv->poll_id = 0;
202 }
203
204 /* child exited, close resources */
205 close (spawn->priv->stdin_fd);
206 close (spawn->priv->stdout_fd);
207 close (spawn->priv->stderr_fd);
208 spawn->priv->stdin_fd = -1;
209 spawn->priv->stdout_fd = -1;
210 spawn->priv->stderr_fd = -1;
211 spawn->priv->child_pid = -1;
212
213 /* use this to detect SIGKILL and SIGQUIT */
214 if (WIFSIGNALED (status)) {
215 retval = WTERMSIG (status);
216 if (retval == SIGQUIT) {
217 g_debug ("the child process was terminated by SIGQUIT");
218 spawn->priv->exit = CD_SPAWN_EXIT_TYPE_SIGQUIT;
219 } else if (retval == SIGKILL) {
220 g_debug ("the child process was terminated by SIGKILL");
221 spawn->priv->exit = CD_SPAWN_EXIT_TYPE_SIGKILL;
222 } else {
223 g_warning ("the child process was terminated by signal %i", WTERMSIG (status));
224 spawn->priv->exit = CD_SPAWN_EXIT_TYPE_SIGKILL;
225 }
226 } else {
227 /* check we are dead and buried */
228 if (!WIFEXITED (status)) {
229 g_warning ("the process did not exit, but waitpid() returned!");
230 return G_SOURCE_CONTINUE;
231 }
232
233 /* get the exit code */
234 retval = WEXITSTATUS (status);
235 if (retval == 0) {
236 g_debug ("the child exited with success");
237 if (spawn->priv->exit == CD_SPAWN_EXIT_TYPE_UNKNOWN)
238 spawn->priv->exit = CD_SPAWN_EXIT_TYPE_SUCCESS;
239 } else if (retval == 254) {
240 g_debug ("backend was exited rather than finished");
241 spawn->priv->exit = CD_SPAWN_EXIT_TYPE_FAILED;
242 } else {
243 g_warning ("the child exited with return code %i", retval);
244 if (spawn->priv->exit == CD_SPAWN_EXIT_TYPE_UNKNOWN)
245 spawn->priv->exit = CD_SPAWN_EXIT_TYPE_FAILED;
246 }
247 }
248
249 /* officially done, although no signal yet */
250 spawn->priv->finished = TRUE;
251
252 /* if we are trying to kill this process, cancel the SIGKILL */
253 if (spawn->priv->kill_id != 0) {
254 g_source_remove (spawn->priv->kill_id);
255 spawn->priv->kill_id = 0;
256 }
257
258 /* don't emit if we just closed an invalid dispatcher */
259 g_debug ("emitting exit %s", cd_spawn_exit_type_enum_to_string (spawn->priv->exit));
260 g_signal_emit (spawn, signals [SIGNAL_EXIT], 0, spawn->priv->exit);
261 return G_SOURCE_REMOVE;
262 }
263
264 /**
265 * cd_spawn_sigkill_cb:
266 **/
267 static gboolean
cd_spawn_sigkill_cb(CdSpawn * spawn)268 cd_spawn_sigkill_cb (CdSpawn *spawn)
269 {
270 gint retval;
271
272 /* check if process has already gone */
273 if (spawn->priv->finished) {
274 g_debug ("already finished, ignoring");
275 return G_SOURCE_REMOVE;
276 }
277
278 /* set this in case the script catches the signal and exits properly */
279 spawn->priv->exit = CD_SPAWN_EXIT_TYPE_SIGKILL;
280
281 g_debug ("sending SIGKILL %ld", (long)spawn->priv->child_pid);
282 retval = kill (spawn->priv->child_pid, SIGKILL);
283 if (retval == EINVAL) {
284 g_warning ("The signum argument is an invalid or unsupported number");
285 return G_SOURCE_REMOVE;
286 }
287 if (retval == EPERM) {
288 g_warning ("You do not have the privilege to send a signal to the process");
289 return G_SOURCE_REMOVE;
290 }
291
292 /* never repeat */
293 return G_SOURCE_REMOVE;
294 }
295
296 /**
297 * cd_spawn_is_running:
298 *
299 * Is this instance controlling a script?
300 *
301 **/
302 gboolean
cd_spawn_is_running(CdSpawn * spawn)303 cd_spawn_is_running (CdSpawn *spawn)
304 {
305 return (spawn->priv->child_pid != -1);
306 }
307
308 /**
309 * cd_spawn_kill:
310 *
311 * We send SIGQUIT and after a few ms SIGKILL (if allowed)
312 *
313 * IMPORTANT: This is not a syncronous operation, and client programs will need
314 * to wait for the ::exit signal.
315 **/
316 gboolean
cd_spawn_kill(CdSpawn * spawn)317 cd_spawn_kill (CdSpawn *spawn)
318 {
319 gint retval;
320
321 g_return_val_if_fail (CD_IS_SPAWN (spawn), FALSE);
322 g_return_val_if_fail (spawn->priv->kill_id == 0, FALSE);
323
324 /* is there a process running? */
325 if (spawn->priv->child_pid == -1) {
326 g_warning ("no child pid to kill!");
327 return FALSE;
328 }
329
330 /* check if process has already gone */
331 if (spawn->priv->finished) {
332 g_debug ("already finished, ignoring");
333 return FALSE;
334 }
335
336 /* set this in case the script catches the signal and exits properly */
337 spawn->priv->exit = CD_SPAWN_EXIT_TYPE_SIGQUIT;
338
339 g_debug ("sending SIGQUIT %ld", (long)spawn->priv->child_pid);
340 retval = kill (spawn->priv->child_pid, SIGQUIT);
341 if (retval == EINVAL) {
342 g_warning ("The signum argument is an invalid or unsupported number");
343 return FALSE;
344 } else if (retval == EPERM) {
345 g_warning ("You do not have the privilege to send a signal to the process");
346 return FALSE;
347 }
348
349 /* the program might not be able to handle SIGQUIT, give it a few seconds and then SIGKILL it */
350 if (spawn->priv->allow_sigkill) {
351 spawn->priv->kill_id = g_timeout_add (CD_SPAWN_SIGKILL_DELAY, (GSourceFunc) cd_spawn_sigkill_cb, spawn);
352 g_source_set_name_by_id (spawn->priv->kill_id, "[CdSpawn] sigkill");
353 }
354 return TRUE;
355 }
356
357 /**
358 * cd_spawn_send_stdin:
359 *
360 * Send new comands to a running (but idle) dispatcher script
361 *
362 **/
363 gboolean
cd_spawn_send_stdin(CdSpawn * spawn,const gchar * command)364 cd_spawn_send_stdin (CdSpawn *spawn, const gchar *command)
365 {
366 gint wrote;
367 gint length;
368 g_autofree gchar *buffer = NULL;
369
370 g_return_val_if_fail (CD_IS_SPAWN (spawn), FALSE);
371
372 /* check if process has already gone */
373 if (spawn->priv->finished) {
374 g_debug ("already finished, ignoring");
375 return FALSE;
376 }
377
378 /* is there a process running? */
379 if (spawn->priv->child_pid == -1) {
380 g_debug ("no child pid");
381 return FALSE;
382 }
383
384 /* buffer always has to have trailing newline */
385 g_debug ("sending '%s'", command);
386 buffer = g_strdup_printf ("%s\n", command);
387
388 /* ITS4: ignore, we generated this */
389 length = strlen (buffer);
390
391 /* write to the waiting process */
392 wrote = write (spawn->priv->stdin_fd, buffer, length);
393 if (wrote != length) {
394 g_warning ("wrote %i/%i bytes on fd %i (%s)", wrote, length, spawn->priv->stdin_fd, strerror (errno));
395 return FALSE;
396 }
397 return TRUE;
398 }
399
400 /**
401 * cd_spawn_argv:
402 **/
403 gboolean
cd_spawn_argv(CdSpawn * spawn,gchar ** argv,gchar ** envp,GError ** error)404 cd_spawn_argv (CdSpawn *spawn, gchar **argv, gchar **envp, GError **error)
405 {
406 gboolean ret = TRUE;
407 guint i;
408 guint len;
409 gint rc;
410 g_autoptr(GError) error_local = NULL;
411
412 g_return_val_if_fail (CD_IS_SPAWN (spawn), FALSE);
413 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
414 g_return_val_if_fail (argv != NULL, FALSE);
415
416 len = g_strv_length (argv);
417 for (i = 0; i < len; i++)
418 g_debug ("argv[%u] '%s'", i, argv[i]);
419 if (envp != NULL) {
420 len = g_strv_length (envp);
421 for (i = 0; i < len; i++)
422 g_debug ("envp[%u] '%s'", i, envp[i]);
423 }
424
425 /* create spawned object for tracking */
426 spawn->priv->finished = FALSE;
427 g_debug ("creating new instance of %s", argv[0]);
428 ret = g_spawn_async_with_pipes (NULL, argv, envp,
429 G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
430 NULL, NULL, &spawn->priv->child_pid,
431 &spawn->priv->stdin_fd,
432 &spawn->priv->stdout_fd,
433 &spawn->priv->stderr_fd,
434 &error_local);
435 if (!ret) {
436 g_set_error (error, 1, 0, "failed to spawn %s: %s", argv[0], error_local->message);
437 return FALSE;
438 }
439
440 /* install an idle handler to check if the child returnd successfully. */
441 rc = fcntl (spawn->priv->stdout_fd, F_SETFL, O_NONBLOCK);
442 if (rc < 0) {
443 g_set_error_literal (error, 1, 0, "stdout fcntl failed");
444 return FALSE;
445 }
446 rc = fcntl (spawn->priv->stderr_fd, F_SETFL, O_NONBLOCK);
447 if (rc < 0) {
448 g_set_error_literal (error, 1, 0, "stderr fcntl failed");
449 return FALSE;
450 }
451
452 /* sanity check */
453 if (spawn->priv->poll_id != 0) {
454 g_warning ("trying to set timeout when already set");
455 g_source_remove (spawn->priv->poll_id);
456 }
457
458 /* poll quickly */
459 spawn->priv->poll_id = g_timeout_add (CD_SPAWN_POLL_DELAY, (GSourceFunc) cd_spawn_check_child, spawn);
460 g_source_set_name_by_id (spawn->priv->poll_id, "[CdSpawn] main poll");
461 return TRUE;
462 }
463
464 /**
465 * cd_spawn_class_init:
466 * @klass: The CdSpawnClass
467 **/
468 static void
cd_spawn_class_init(CdSpawnClass * klass)469 cd_spawn_class_init (CdSpawnClass *klass)
470 {
471 GObjectClass *object_class = G_OBJECT_CLASS (klass);
472
473 object_class->finalize = cd_spawn_finalize;
474
475 signals [SIGNAL_EXIT] =
476 g_signal_new ("exit",
477 G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
478 0, NULL, NULL, g_cclosure_marshal_VOID__INT,
479 G_TYPE_NONE, 1, G_TYPE_INT);
480 signals [SIGNAL_STDOUT] =
481 g_signal_new ("stdout",
482 G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
483 0, NULL, NULL, g_cclosure_marshal_VOID__STRING,
484 G_TYPE_NONE, 1, G_TYPE_STRING);
485 signals [SIGNAL_STDERR] =
486 g_signal_new ("stderr",
487 G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
488 0, NULL, NULL, g_cclosure_marshal_VOID__STRING,
489 G_TYPE_NONE, 1, G_TYPE_STRING);
490
491 g_type_class_add_private (klass, sizeof (CdSpawnPrivate));
492 }
493
494 /**
495 * cd_spawn_init:
496 * @spawn: This class instance
497 **/
498 static void
cd_spawn_init(CdSpawn * spawn)499 cd_spawn_init (CdSpawn *spawn)
500 {
501 spawn->priv = CD_SPAWN_GET_PRIVATE (spawn);
502
503 spawn->priv->child_pid = -1;
504 spawn->priv->stdout_fd = -1;
505 spawn->priv->stderr_fd = -1;
506 spawn->priv->stdin_fd = -1;
507 spawn->priv->allow_sigkill = TRUE;
508 spawn->priv->exit = CD_SPAWN_EXIT_TYPE_UNKNOWN;
509
510 spawn->priv->stdout_buf = g_string_new ("");
511 spawn->priv->stderr_buf = g_string_new ("");
512 }
513
514 /**
515 * cd_spawn_finalize:
516 * @object: The object to finalize
517 **/
518 static void
cd_spawn_finalize(GObject * object)519 cd_spawn_finalize (GObject *object)
520 {
521 CdSpawn *spawn;
522
523 g_return_if_fail (object != NULL);
524 g_return_if_fail (CD_IS_SPAWN (object));
525
526 spawn = CD_SPAWN (object);
527
528 g_return_if_fail (spawn->priv != NULL);
529
530 /* disconnect the poll in case we were cancelled before completion */
531 if (spawn->priv->poll_id != 0) {
532 g_source_remove (spawn->priv->poll_id);
533 spawn->priv->poll_id = 0;
534 }
535
536 /* disconnect the SIGKILL check */
537 if (spawn->priv->kill_id != 0) {
538 g_source_remove (spawn->priv->kill_id);
539 spawn->priv->kill_id = 0;
540 }
541
542 /* still running? */
543 if (spawn->priv->stdin_fd != -1) {
544 g_debug ("killing as still running in finalize");
545 cd_spawn_kill (spawn);
546 /* just hope the script responded to SIGQUIT */
547 if (spawn->priv->kill_id != 0)
548 g_source_remove (spawn->priv->kill_id);
549 }
550
551 /* free the buffers */
552 g_string_free (spawn->priv->stdout_buf, TRUE);
553 g_string_free (spawn->priv->stderr_buf, TRUE);
554
555 G_OBJECT_CLASS (cd_spawn_parent_class)->finalize (object);
556 }
557
558 /**
559 * cd_spawn_new:
560 *
561 * Return value: a new CdSpawn object.
562 **/
563 CdSpawn *
cd_spawn_new(void)564 cd_spawn_new (void)
565 {
566 CdSpawn *spawn;
567 spawn = g_object_new (CD_TYPE_SPAWN, NULL);
568 return CD_SPAWN (spawn);
569 }
570