2 * Copyright (C) 2010-2011 Robert Ancell.
3 * Author: Robert Ancell <robert.ancell@canonical.com>
5 * This program is free software: you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option) any later
8 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
20 #include <glib/gstdio.h>
31 static guint signals[LAST_SIGNAL] = { 0 };
35 /* Function to run inside subprocess before exec */
36 ProcessRunFunc run_func;
37 gpointer run_func_data;
46 /* TRUE to clear the environment in this process */
47 gboolean clear_environment;
49 /* Environment variables to set */
55 /* Exit status of process */
58 /* TRUE if stopping this process (waiting for child process to stop) */
61 /* Timeout waiting for process to quit */
64 /* Watch on process */
68 G_DEFINE_TYPE (Process, process, G_TYPE_OBJECT);
70 static Process *current_process = NULL;
71 static GHashTable *processes = NULL;
72 static pid_t signal_pid;
73 static int signal_pipe[2];
76 process_get_current (void)
79 return current_process;
81 current_process = process_new (NULL, NULL);
82 current_process->priv->pid = getpid ();
84 return current_process;
88 process_new (ProcessRunFunc run_func, gpointer run_func_data)
90 Process *process = g_object_new (PROCESS_TYPE, NULL);
91 process->priv->run_func = run_func;
92 process->priv->run_func_data = run_func_data;
97 process_set_log_file (Process *process, const gchar *path, gboolean log_stdout)
99 g_return_if_fail (process != NULL);
100 g_free (process->priv->log_file);
101 process->priv->log_file = g_strdup (path);
102 process->priv->log_stdout = log_stdout;
106 process_set_clear_environment (Process *process, gboolean clear_environment)
108 g_return_if_fail (process != NULL);
109 process->priv->clear_environment = clear_environment;
113 process_get_clear_environment (Process *process)
115 g_return_val_if_fail (process != NULL, FALSE);
116 return process->priv->clear_environment;
120 process_set_env (Process *process, const gchar *name, const gchar *value)
122 g_return_if_fail (process != NULL);
123 g_return_if_fail (name != NULL);
124 g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));
128 process_get_env (Process *process, const gchar *name)
130 g_return_val_if_fail (process != NULL, NULL);
131 g_return_val_if_fail (name != NULL, NULL);
132 return g_hash_table_lookup (process->priv->env, name);
136 process_set_command (Process *process, const gchar *command)
138 g_return_if_fail (process != NULL);
140 g_free (process->priv->command);
141 process->priv->command = g_strdup (command);
145 process_get_command (Process *process)
147 g_return_val_if_fail (process != NULL, NULL);
148 return process->priv->command;
152 process_watch_cb (GPid pid, gint status, gpointer data)
154 Process *process = data;
156 process->priv->watch = 0;
157 process->priv->exit_status = status;
159 if (WIFEXITED (status))
160 g_debug ("Process %d exited with return value %d", pid, WEXITSTATUS (status));
161 else if (WIFSIGNALED (status))
162 g_debug ("Process %d terminated with signal %d", pid, WTERMSIG (status));
164 if (process->priv->quit_timeout)
165 g_source_remove (process->priv->quit_timeout);
166 process->priv->quit_timeout = 0;
167 process->priv->pid = 0;
168 g_hash_table_remove (processes, GINT_TO_POINTER (pid));
170 g_signal_emit (process, signals[STOPPED], 0);
174 process_start (Process *process, gboolean block)
178 gchar **env_keys, **env_values;
183 GError *error = NULL;
185 g_return_val_if_fail (process != NULL, FALSE);
186 g_return_val_if_fail (process->priv->command != NULL, FALSE);
187 g_return_val_if_fail (process->priv->pid == 0, FALSE);
189 if (!g_shell_parse_argv (process->priv->command, &argc, &argv, &error))
191 g_warning ("Error parsing command %s: %s", process->priv->command, error->message);
195 if (process->priv->log_file)
199 /* Move old file out of the way */
200 old_filename = g_strdup_printf ("%s.old", process->priv->log_file);
201 rename (process->priv->log_file, old_filename);
202 g_free (old_filename);
204 /* Create new file and log to it */
205 log_fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
207 g_warning ("Failed to open log file %s: %s", process->priv->log_file, g_strerror (errno));
210 /* Work out variables to set */
211 env_length = g_hash_table_size (process->priv->env);
212 env_keys = g_malloc (sizeof (gchar *) * env_length);
213 env_values = g_malloc (sizeof (gchar *) * env_length);
214 keys = g_hash_table_get_keys (process->priv->env);
215 for (i = 0, link = keys; i < env_length; i++, link = link->next)
217 env_keys[i] = link->data;
218 env_values[i] = g_hash_table_lookup (process->priv->env, env_keys[i]);
225 /* Do custom setup */
226 if (process->priv->run_func)
227 process->priv->run_func (process, process->priv->run_func_data);
229 /* Redirect output to logfile */
232 if (process->priv->log_stdout)
233 dup2 (log_fd, STDOUT_FILENO);
234 dup2 (log_fd, STDERR_FILENO);
238 /* Set environment */
239 if (process->priv->clear_environment)
245 for (i = 0; i < env_length; i++)
246 setenv (env_keys[i], env_values[i], TRUE);
248 execvp (argv[0], argv);
249 _exit (EXIT_FAILURE);
259 g_warning ("Failed to fork: %s", strerror (errno));
263 g_debug ("Launching process %d: %s", pid, process->priv->command);
265 process->priv->pid = pid;
270 waitpid (process->priv->pid, &exit_status, 0);
271 process_watch_cb (process->priv->pid, exit_status, process);
275 g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
276 process->priv->watch = g_child_watch_add (process->priv->pid, process_watch_cb, process);
283 process_get_is_running (Process *process)
285 g_return_val_if_fail (process != NULL, FALSE);
286 return process->priv->pid != 0;
290 process_get_pid (Process *process)
292 g_return_val_if_fail (process != NULL, 0);
293 return process->priv->pid;
297 process_signal (Process *process, int signum)
299 g_return_if_fail (process != NULL);
301 if (process->priv->pid == 0)
304 g_debug ("Sending signal %d to process %d", signum, process->priv->pid);
306 if (kill (process->priv->pid, signum) < 0)
308 /* Ignore ESRCH, we will pick that up in our wait */
310 g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
315 quit_timeout_cb (Process *process)
317 process->priv->quit_timeout = 0;
318 process_signal (process, SIGKILL);
323 process_stop (Process *process)
325 g_return_if_fail (process != NULL);
327 if (process->priv->stopping)
329 process->priv->stopping = TRUE;
331 /* If already stopped then we're done! */
332 if (process->priv->pid == 0)
335 /* Send SIGTERM, and then SIGKILL if no response */
336 process->priv->quit_timeout = g_timeout_add (5000, (GSourceFunc) quit_timeout_cb, process);
337 process_signal (process, SIGTERM);
341 process_get_exit_status (Process *process)
343 g_return_val_if_fail (process != NULL, -1);
344 return process->priv->exit_status;
348 process_init (Process *process)
350 process->priv = G_TYPE_INSTANCE_GET_PRIVATE (process, PROCESS_TYPE, ProcessPrivate);
351 process->priv->env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
355 process_stopped (Process *process)
360 process_finalize (GObject *object)
364 self = PROCESS (object);
366 if (self->priv->pid > 0)
367 g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
369 g_free (self->priv->log_file);
370 g_free (self->priv->command);
371 g_hash_table_unref (self->priv->env);
372 if (self->priv->quit_timeout)
373 g_source_remove (self->priv->quit_timeout);
374 if (self->priv->watch)
375 g_source_remove (self->priv->watch);
378 kill (self->priv->pid, SIGTERM);
380 G_OBJECT_CLASS (process_parent_class)->finalize (object);
384 signal_cb (int signum, siginfo_t *info, void *data)
386 /* Check if we are from a forked process that hasn't updated the signal handlers or execed.
387 If so, then we should just quit */
388 if (getpid () != signal_pid)
389 _exit (EXIT_SUCCESS);
391 /* Write signal to main thread, if something goes wrong just close the pipe so it is detected on the other end */
392 if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
393 write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
394 close (signal_pipe[1]);
398 handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
405 if (read (signal_pipe[0], &signo, sizeof (int)) != sizeof (int) ||
406 read (signal_pipe[0], &pid, sizeof (pid_t)) != sizeof (pid_t))
408 g_warning ("Error reading from signal pipe: %s", strerror (errno));
412 g_debug ("Got signal %d from process %d", signo, pid);
414 process = g_hash_table_lookup (processes, GINT_TO_POINTER (pid));
416 process = process_get_current ();
418 g_signal_emit (process, signals[GOT_SIGNAL], 0, signo);
424 process_class_init (ProcessClass *klass)
426 GObjectClass *object_class = G_OBJECT_CLASS (klass);
427 struct sigaction action;
429 klass->stopped = process_stopped;
430 object_class->finalize = process_finalize;
432 g_type_class_add_private (klass, sizeof (ProcessPrivate));
435 g_signal_new ("got-data",
436 G_TYPE_FROM_CLASS (klass),
438 G_STRUCT_OFFSET (ProcessClass, got_data),
442 signals[GOT_SIGNAL] =
443 g_signal_new ("got-signal",
444 G_TYPE_FROM_CLASS (klass),
446 G_STRUCT_OFFSET (ProcessClass, got_signal),
449 G_TYPE_NONE, 1, G_TYPE_INT);
451 g_signal_new ("stopped",
452 G_TYPE_FROM_CLASS (klass),
454 G_STRUCT_OFFSET (ProcessClass, stopped),
459 /* Catch signals and feed them to the main loop via a pipe */
460 processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
461 signal_pid = getpid ();
462 if (pipe (signal_pipe) != 0)
463 g_critical ("Failed to create signal pipe");
464 fcntl (signal_pipe[0], F_SETFD, FD_CLOEXEC);
465 fcntl (signal_pipe[1], F_SETFD, FD_CLOEXEC);
466 g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
467 action.sa_sigaction = signal_cb;
468 sigemptyset (&action.sa_mask);
469 action.sa_flags = SA_SIGINFO;
470 sigaction (SIGTERM, &action, NULL);
471 sigaction (SIGINT, &action, NULL);
472 sigaction (SIGHUP, &action, NULL);
473 sigaction (SIGUSR1, &action, NULL);
474 sigaction (SIGUSR2, &action, NULL);