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