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>
33 static guint signals[LAST_SIGNAL] = { 0 };
37 /* Environment variables */
43 /* Path of file to log to */
46 /* Timeout waiting for process to quit */
53 G_DEFINE_TYPE (Process, process, G_TYPE_OBJECT);
55 static Process *current_process = NULL;
56 static GHashTable *processes = NULL;
57 static int signal_pipe[2];
60 process_get_current (void)
63 return current_process;
65 current_process = process_new ();
66 current_process->priv->pid = getpid ();
68 return current_process;
74 return g_object_new (PROCESS_TYPE, NULL);
78 process_set_log_file (Process *process, const gchar *log_file)
80 g_return_if_fail (process != NULL);
82 g_free (process->priv->log_file);
83 process->priv->log_file = g_strdup (log_file);
87 process_get_log_file (Process *process)
89 g_return_val_if_fail (process != NULL, NULL);
90 return process->priv->log_file;
94 process_set_env (Process *process, const gchar *name, const gchar *value)
96 g_return_if_fail (process != NULL);
97 g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));
101 process_watch_cb (GPid pid, gint status, gpointer data)
103 Process *process = data;
105 if (WIFEXITED (status))
107 g_debug ("Process %d exited with return value %d", pid, WEXITSTATUS (status));
108 g_signal_emit (process, signals[EXITED], 0, WEXITSTATUS (status));
110 else if (WIFSIGNALED (status))
112 g_debug ("Process %d terminated with signal %d", pid, WTERMSIG (status));
113 g_signal_emit (process, signals[TERMINATED], 0, WTERMSIG (status));
116 if (process->priv->quit_timeout)
117 g_source_remove (process->priv->quit_timeout);
118 process->priv->quit_timeout = 0;
119 process->priv->pid = 0;
120 g_hash_table_remove (processes, GINT_TO_POINTER (pid));
122 g_signal_emit (process, signals[STOPPED], 0);
126 run_process (Process *process, char *const argv[])
132 /* FIXME: Close existing file descriptors */
134 /* Make input non-blocking */
135 fd = g_open ("/dev/null", O_RDONLY);
136 dup2 (fd, STDIN_FILENO);
139 /* Set environment */
141 g_hash_table_iter_init (&iter, process->priv->env);
142 while (g_hash_table_iter_next (&iter, &key, &value))
143 g_setenv ((gchar *)key, (gchar *)value, TRUE);
145 /* Set SIGUSR1 to ignore so the child process can indicate it when it is ready */
146 // FIXME: Should be in the xserver only
147 signal (SIGUSR1, SIG_IGN);
149 /* Make this process its own session so */
151 g_warning ("Failed to make process a new session: %s", strerror (errno));
153 if (process->priv->user)
157 if (initgroups (user_get_name (process->priv->user), user_get_gid (process->priv->user)) < 0)
159 g_warning ("Failed to initialize supplementary groups for %s: %s", user_get_name (process->priv->user), strerror (errno));
160 _exit (EXIT_FAILURE);
163 if (setgid (user_get_gid (process->priv->user)) != 0)
165 g_warning ("Failed to set group ID to %d: %s", user_get_gid (process->priv->user), strerror (errno));
166 _exit (EXIT_FAILURE);
169 if (setuid (user_get_uid (process->priv->user)) != 0)
171 g_warning ("Failed to set user ID to %d: %s", user_get_uid (process->priv->user), strerror (errno));
172 _exit (EXIT_FAILURE);
176 if (chdir (user_get_home_directory (process->priv->user)) != 0)
178 g_warning ("Failed to change to home directory %s: %s", user_get_home_directory (process->priv->user), strerror (errno));
179 _exit (EXIT_FAILURE);
183 /* Redirect output to logfile */
184 if (process->priv->log_file)
188 fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
190 g_warning ("Failed to open log file %s: %s", process->priv->log_file, g_strerror (errno));
193 dup2 (fd, STDOUT_FILENO);
194 dup2 (fd, STDERR_FILENO);
199 execv (argv[0], argv);
201 g_warning ("Error executing child process %s: %s", argv[0], g_strerror (errno));
202 _exit (EXIT_FAILURE);
206 process_start (Process *process,
208 const gchar *working_dir,
209 const gchar *command,
220 g_return_val_if_fail (process != NULL, FALSE);
221 g_return_val_if_fail (process->priv->pid == 0, FALSE);
223 process->priv->user = g_object_ref (user);
225 /* Create the log file owned by the target user */
226 if (process->priv->log_file)
228 gint fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
230 if (getuid () == 0 && chown (process->priv->log_file, user_get_uid (process->priv->user), user_get_gid (process->priv->user)) != 0)
231 g_warning ("Failed to set process log file ownership: %s", strerror (errno));
234 result = g_shell_parse_argv (command, &argc, &argv, error);
241 g_warning ("Failed to fork: %s", strerror (errno));
246 run_process (process, argv);
249 string = g_string_new ("");
250 g_hash_table_iter_init (&iter, process->priv->env);
251 while (g_hash_table_iter_next (&iter, &key, &value))
252 g_string_append_printf (string, "%s=%s ", (gchar *)key, (gchar *)value);
253 g_string_append (string, command);
254 g_debug ("Launching process %d: %s", pid, string->str);
255 g_string_free (string, TRUE);
257 process->priv->pid = pid;
259 g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
260 g_child_watch_add (process->priv->pid, process_watch_cb, process);
262 g_signal_emit (process, signals[STARTED], 0);
268 process_get_is_running (Process *process)
270 g_return_val_if_fail (process != NULL, FALSE);
271 return process->priv->pid != 0;
275 process_get_pid (Process *process)
277 g_return_val_if_fail (process != NULL, 0);
278 return process->priv->pid;
282 process_signal (Process *process, int signum)
284 g_return_if_fail (process != NULL);
286 if (process->priv->pid == 0)
289 g_debug ("Sending signal %d to process %d", signum, process->priv->pid);
291 if (kill (process->priv->pid, signum) < 0)
292 g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
296 quit_timeout_cb (Process *process)
298 process->priv->quit_timeout = 0;
299 process_signal (process, SIGKILL);
304 process_stop (Process *process)
306 /* Send SIGTERM, and then SIGKILL if no response */
307 process->priv->quit_timeout = g_timeout_add (5000, (GSourceFunc) quit_timeout_cb, process);
308 process_signal (process, SIGTERM);
312 process_init (Process *process)
314 process->priv = G_TYPE_INSTANCE_GET_PRIVATE (process, PROCESS_TYPE, ProcessPrivate);
315 process->priv->env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
319 process_finalize (GObject *object)
323 self = PROCESS (object);
325 if (self->priv->user)
326 g_object_unref (self->priv->user);
328 if (self->priv->pid > 0)
329 g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
332 kill (self->priv->pid, SIGTERM);
334 g_hash_table_unref (self->priv->env);
336 G_OBJECT_CLASS (process_parent_class)->finalize (object);
340 signal_cb (int signum, siginfo_t *info, void *data)
342 /* NOTE: Using g_printerr as can't call g_warning from a signal callback */
343 if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
344 write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
345 g_printerr ("Failed to write to signal pipe: %s", strerror (errno));
349 handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
355 if (read (signal_pipe[0], &signo, sizeof (int)) < 0 ||
356 read (signal_pipe[0], &pid, sizeof (pid_t)) < 0)
358 g_warning ("Error reading from signal pipe: %s", strerror (errno));
362 g_debug ("Got signal %d from process %d", signo, pid);
364 process = g_hash_table_lookup (processes, GINT_TO_POINTER (pid));
366 process = process_get_current ();
368 g_signal_emit (process, signals[GOT_SIGNAL], 0, signo);
374 process_class_init (ProcessClass *klass)
376 GObjectClass *object_class = G_OBJECT_CLASS (klass);
377 struct sigaction action;
379 object_class->finalize = process_finalize;
381 g_type_class_add_private (klass, sizeof (ProcessPrivate));
384 g_signal_new ("started",
385 G_TYPE_FROM_CLASS (klass),
387 G_STRUCT_OFFSET (ProcessClass, started),
389 g_cclosure_marshal_VOID__VOID,
392 g_signal_new ("got-data",
393 G_TYPE_FROM_CLASS (klass),
395 G_STRUCT_OFFSET (ProcessClass, got_data),
397 g_cclosure_marshal_VOID__VOID,
399 signals[GOT_SIGNAL] =
400 g_signal_new ("got-signal",
401 G_TYPE_FROM_CLASS (klass),
403 G_STRUCT_OFFSET (ProcessClass, got_signal),
405 g_cclosure_marshal_VOID__INT,
406 G_TYPE_NONE, 1, G_TYPE_INT);
408 g_signal_new ("exited",
409 G_TYPE_FROM_CLASS (klass),
411 G_STRUCT_OFFSET (ProcessClass, exited),
413 g_cclosure_marshal_VOID__INT,
414 G_TYPE_NONE, 1, G_TYPE_INT);
415 signals[TERMINATED] =
416 g_signal_new ("terminated",
417 G_TYPE_FROM_CLASS (klass),
419 G_STRUCT_OFFSET (ProcessClass, terminated),
421 g_cclosure_marshal_VOID__INT,
422 G_TYPE_NONE, 1, G_TYPE_INT);
424 g_signal_new ("stopped",
425 G_TYPE_FROM_CLASS (klass),
427 G_STRUCT_OFFSET (ProcessClass, stopped),
429 g_cclosure_marshal_VOID__VOID,
432 /* Catch signals and feed them to the main loop via a pipe */
433 processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
434 if (pipe (signal_pipe) != 0)
435 g_critical ("Failed to create signal pipe");
436 g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
437 action.sa_sigaction = signal_cb;
438 sigemptyset (&action.sa_mask);
439 action.sa_flags = SA_SIGINFO;
440 sigaction (SIGTERM, &action, NULL);
441 sigaction (SIGINT, &action, NULL);
442 sigaction (SIGHUP, &action, NULL);
443 sigaction (SIGUSR1, &action, NULL);
444 sigaction (SIGUSR2, &action, NULL);