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_get_env (Process *process, const gchar *name)
103 g_return_val_if_fail (process != NULL, FALSE);
104 return g_hash_table_lookup (process->priv->env, name);
108 process_watch_cb (GPid pid, gint status, gpointer data)
110 Process *process = data;
112 if (WIFEXITED (status))
114 g_debug ("Process %d exited with return value %d", pid, WEXITSTATUS (status));
115 g_signal_emit (process, signals[EXITED], 0, WEXITSTATUS (status));
117 else if (WIFSIGNALED (status))
119 g_debug ("Process %d terminated with signal %d", pid, WTERMSIG (status));
120 g_signal_emit (process, signals[TERMINATED], 0, WTERMSIG (status));
123 if (process->priv->quit_timeout)
124 g_source_remove (process->priv->quit_timeout);
125 process->priv->quit_timeout = 0;
126 process->priv->pid = 0;
127 g_hash_table_remove (processes, GINT_TO_POINTER (pid));
129 g_signal_emit (process, signals[STOPPED], 0);
133 run_process (Process *process, char *const argv[])
139 /* FIXME: Close existing file descriptors */
141 /* Make input non-blocking */
142 fd = g_open ("/dev/null", O_RDONLY);
143 dup2 (fd, STDIN_FILENO);
146 /* Set environment */
148 g_hash_table_iter_init (&iter, process->priv->env);
149 while (g_hash_table_iter_next (&iter, &key, &value))
150 g_setenv ((gchar *)key, (gchar *)value, TRUE);
152 /* Set SIGUSR1 to ignore so the child process can indicate it when it is ready */
153 // FIXME: Should be in the xserver only
154 signal (SIGUSR1, SIG_IGN);
156 /* Make this process its own session so */
158 g_warning ("Failed to make process a new session: %s", strerror (errno));
160 if (process->priv->user)
164 if (initgroups (user_get_name (process->priv->user), user_get_gid (process->priv->user)) < 0)
166 g_warning ("Failed to initialize supplementary groups for %s: %s", user_get_name (process->priv->user), strerror (errno));
167 _exit (EXIT_FAILURE);
170 if (setgid (user_get_gid (process->priv->user)) != 0)
172 g_warning ("Failed to set group ID to %d: %s", user_get_gid (process->priv->user), strerror (errno));
173 _exit (EXIT_FAILURE);
176 if (setuid (user_get_uid (process->priv->user)) != 0)
178 g_warning ("Failed to set user ID to %d: %s", user_get_uid (process->priv->user), strerror (errno));
179 _exit (EXIT_FAILURE);
183 if (chdir (user_get_home_directory (process->priv->user)) != 0)
185 g_warning ("Failed to change to home directory %s: %s", user_get_home_directory (process->priv->user), strerror (errno));
186 _exit (EXIT_FAILURE);
190 /* Redirect output to logfile */
191 if (process->priv->log_file)
195 fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
197 g_warning ("Failed to open log file %s: %s", process->priv->log_file, g_strerror (errno));
200 dup2 (fd, STDOUT_FILENO);
201 dup2 (fd, STDERR_FILENO);
206 execv (argv[0], argv);
208 g_warning ("Error executing child process %s: %s", argv[0], g_strerror (errno));
209 _exit (EXIT_FAILURE);
213 process_start (Process *process,
215 const gchar *working_dir,
216 const gchar *command,
227 g_return_val_if_fail (process != NULL, FALSE);
228 g_return_val_if_fail (process->priv->pid == 0, FALSE);
230 process->priv->user = g_object_ref (user);
232 /* Create the log file owned by the target user */
233 if (process->priv->log_file)
235 gint fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
237 if (getuid () == 0 && chown (process->priv->log_file, user_get_uid (process->priv->user), user_get_gid (process->priv->user)) != 0)
238 g_warning ("Failed to set process log file ownership: %s", strerror (errno));
241 result = g_shell_parse_argv (command, &argc, &argv, error);
248 g_warning ("Failed to fork: %s", strerror (errno));
253 run_process (process, argv);
256 string = g_string_new ("");
257 g_hash_table_iter_init (&iter, process->priv->env);
258 while (g_hash_table_iter_next (&iter, &key, &value))
259 g_string_append_printf (string, "%s=%s ", (gchar *)key, (gchar *)value);
260 g_string_append (string, command);
261 g_debug ("Launching process %d: %s", pid, string->str);
262 g_string_free (string, TRUE);
264 process->priv->pid = pid;
266 g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
267 g_child_watch_add (process->priv->pid, process_watch_cb, process);
269 g_signal_emit (process, signals[STARTED], 0);
275 process_get_is_running (Process *process)
277 g_return_val_if_fail (process != NULL, FALSE);
278 return process->priv->pid != 0;
282 process_get_pid (Process *process)
284 g_return_val_if_fail (process != NULL, 0);
285 return process->priv->pid;
289 process_signal (Process *process, int signum)
291 g_return_if_fail (process != NULL);
293 if (process->priv->pid == 0)
296 g_debug ("Sending signal %d to process %d", signum, process->priv->pid);
298 if (kill (process->priv->pid, signum) < 0)
299 g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
303 quit_timeout_cb (Process *process)
305 process->priv->quit_timeout = 0;
306 process_signal (process, SIGKILL);
311 process_stop (Process *process)
313 /* Send SIGTERM, and then SIGKILL if no response */
314 process->priv->quit_timeout = g_timeout_add (5000, (GSourceFunc) quit_timeout_cb, process);
315 process_signal (process, SIGTERM);
319 process_init (Process *process)
321 process->priv = G_TYPE_INSTANCE_GET_PRIVATE (process, PROCESS_TYPE, ProcessPrivate);
322 process->priv->env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
326 process_finalize (GObject *object)
330 self = PROCESS (object);
332 if (self->priv->user)
333 g_object_unref (self->priv->user);
335 if (self->priv->pid > 0)
336 g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
339 kill (self->priv->pid, SIGTERM);
341 g_hash_table_unref (self->priv->env);
343 G_OBJECT_CLASS (process_parent_class)->finalize (object);
347 signal_cb (int signum, siginfo_t *info, void *data)
349 /* NOTE: Using g_printerr as can't call g_warning from a signal callback */
350 if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
351 write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
352 g_printerr ("Failed to write to signal pipe: %s", strerror (errno));
356 handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
362 if (read (signal_pipe[0], &signo, sizeof (int)) < 0 ||
363 read (signal_pipe[0], &pid, sizeof (pid_t)) < 0)
365 g_warning ("Error reading from signal pipe: %s", strerror (errno));
369 g_debug ("Got signal %d from process %d", signo, pid);
371 process = g_hash_table_lookup (processes, GINT_TO_POINTER (pid));
373 process = process_get_current ();
375 g_signal_emit (process, signals[GOT_SIGNAL], 0, signo);
381 process_class_init (ProcessClass *klass)
383 GObjectClass *object_class = G_OBJECT_CLASS (klass);
384 struct sigaction action;
386 object_class->finalize = process_finalize;
388 g_type_class_add_private (klass, sizeof (ProcessPrivate));
391 g_signal_new ("started",
392 G_TYPE_FROM_CLASS (klass),
394 G_STRUCT_OFFSET (ProcessClass, started),
396 g_cclosure_marshal_VOID__VOID,
399 g_signal_new ("got-data",
400 G_TYPE_FROM_CLASS (klass),
402 G_STRUCT_OFFSET (ProcessClass, got_data),
404 g_cclosure_marshal_VOID__VOID,
406 signals[GOT_SIGNAL] =
407 g_signal_new ("got-signal",
408 G_TYPE_FROM_CLASS (klass),
410 G_STRUCT_OFFSET (ProcessClass, got_signal),
412 g_cclosure_marshal_VOID__INT,
413 G_TYPE_NONE, 1, G_TYPE_INT);
415 g_signal_new ("exited",
416 G_TYPE_FROM_CLASS (klass),
418 G_STRUCT_OFFSET (ProcessClass, exited),
420 g_cclosure_marshal_VOID__INT,
421 G_TYPE_NONE, 1, G_TYPE_INT);
422 signals[TERMINATED] =
423 g_signal_new ("terminated",
424 G_TYPE_FROM_CLASS (klass),
426 G_STRUCT_OFFSET (ProcessClass, terminated),
428 g_cclosure_marshal_VOID__INT,
429 G_TYPE_NONE, 1, G_TYPE_INT);
431 g_signal_new ("stopped",
432 G_TYPE_FROM_CLASS (klass),
434 G_STRUCT_OFFSET (ProcessClass, stopped),
436 g_cclosure_marshal_VOID__VOID,
439 /* Catch signals and feed them to the main loop via a pipe */
440 processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
441 if (pipe (signal_pipe) != 0)
442 g_critical ("Failed to create signal pipe");
443 g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
444 action.sa_sigaction = signal_cb;
445 sigemptyset (&action.sa_mask);
446 action.sa_flags = SA_SIGINFO;
447 sigaction (SIGTERM, &action, NULL);
448 sigaction (SIGINT, &action, NULL);
449 sigaction (SIGHUP, &action, NULL);
450 sigaction (SIGUSR1, &action, NULL);
451 sigaction (SIGUSR2, &action, NULL);