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>
34 static guint signals[LAST_SIGNAL] = { 0 };
38 /* Environment variables */
44 /* Working directory */
45 gchar *working_directory;
50 /* Path of file to log to */
56 /* Timeout waiting for process to quit */
60 G_DEFINE_TYPE (Process, process, G_TYPE_OBJECT);
62 static Process *current_process = NULL;
63 static GHashTable *processes = NULL;
64 static int signal_pipe[2];
67 process_get_current (void)
70 return current_process;
72 current_process = process_new ();
73 current_process->priv->pid = getpid ();
75 return current_process;
81 return g_object_new (PROCESS_TYPE, NULL);
85 process_set_command (Process *process, const gchar *command)
87 g_return_if_fail (process != NULL);
89 g_free (process->priv->command);
90 process->priv->command = g_strdup (command);
94 process_get_command (Process *process)
96 g_return_val_if_fail (process != NULL, NULL);
97 return process->priv->command;
101 process_set_log_file (Process *process, const gchar *log_file)
103 g_return_if_fail (process != NULL);
105 g_free (process->priv->log_file);
106 process->priv->log_file = g_strdup (log_file);
110 process_get_log_file (Process *process)
112 g_return_val_if_fail (process != NULL, NULL);
113 return process->priv->log_file;
117 process_set_working_directory (Process *process, const gchar *working_directory)
119 g_return_if_fail (process != NULL);
121 g_free (process->priv->working_directory);
122 process->priv->working_directory = g_strdup (working_directory);
126 process_get_working_directory (Process *process)
128 g_return_val_if_fail (process != NULL, NULL);
129 return process->priv->working_directory;
133 process_set_user (Process *process, User *user)
135 g_return_if_fail (process != NULL);
137 if (process->priv->user)
138 g_object_unref (process->priv->user);
139 process->priv->user = g_object_ref (user);
143 process_get_user (Process *process)
145 g_return_val_if_fail (process != NULL, NULL);
146 return process->priv->user;
150 process_set_env (Process *process, const gchar *name, const gchar *value)
152 g_return_if_fail (process != NULL);
153 g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));
157 process_get_env (Process *process, const gchar *name)
159 g_return_val_if_fail (process != NULL, FALSE);
160 return g_hash_table_lookup (process->priv->env, name);
164 process_watch_cb (GPid pid, gint status, gpointer data)
166 Process *process = data;
168 if (WIFEXITED (status))
170 g_debug ("Process %d exited with return value %d", pid, WEXITSTATUS (status));
171 g_signal_emit (process, signals[EXITED], 0, WEXITSTATUS (status));
173 else if (WIFSIGNALED (status))
175 g_debug ("Process %d terminated with signal %d", pid, WTERMSIG (status));
176 g_signal_emit (process, signals[TERMINATED], 0, WTERMSIG (status));
179 if (process->priv->quit_timeout)
180 g_source_remove (process->priv->quit_timeout);
181 process->priv->quit_timeout = 0;
182 process->priv->pid = 0;
183 g_hash_table_remove (processes, GINT_TO_POINTER (pid));
185 g_signal_emit (process, signals[STOPPED], 0);
189 process_run (Process *process)
193 GError *error = NULL;
195 if (!g_shell_parse_argv (process->priv->command, &argc, &argv, &error))
197 g_warning ("Error parsing command %s: %s", process->priv->command, error->message);
198 _exit (EXIT_FAILURE);
201 execv (argv[0], argv);
203 g_warning ("Error executing child process %s: %s", argv[0], g_strerror (errno));
204 _exit (EXIT_FAILURE);
208 run (Process *process)
213 /* FIXME: Close existing file descriptors */
215 /* Set environment */
217 g_hash_table_iter_init (&iter, process->priv->env);
218 while (g_hash_table_iter_next (&iter, &key, &value))
219 g_setenv ((gchar *)key, (gchar *)value, TRUE);
221 /* Make this process its own session */
223 g_warning ("Failed to make process a new session: %s", strerror (errno));
225 if (process->priv->user)
229 if (initgroups (user_get_name (process->priv->user), user_get_gid (process->priv->user)) < 0)
231 g_warning ("Failed to initialize supplementary groups for %s: %s", user_get_name (process->priv->user), strerror (errno));
232 _exit (EXIT_FAILURE);
235 if (setgid (user_get_gid (process->priv->user)) != 0)
237 g_warning ("Failed to set group ID to %d: %s", user_get_gid (process->priv->user), strerror (errno));
238 _exit (EXIT_FAILURE);
241 if (setuid (user_get_uid (process->priv->user)) != 0)
243 g_warning ("Failed to set user ID to %d: %s", user_get_uid (process->priv->user), strerror (errno));
244 _exit (EXIT_FAILURE);
248 if (chdir (user_get_home_directory (process->priv->user)) != 0)
250 g_warning ("Failed to change to home directory %s: %s", user_get_home_directory (process->priv->user), strerror (errno));
251 _exit (EXIT_FAILURE);
255 /* Redirect output to logfile */
256 if (process->priv->log_file)
260 fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
262 g_warning ("Failed to open log file %s: %s", process->priv->log_file, g_strerror (errno));
265 dup2 (fd, STDOUT_FILENO);
266 dup2 (fd, STDERR_FILENO);
271 g_signal_emit (process, signals[RUN], 0);
275 process_start (Process *process)
282 g_return_val_if_fail (process != NULL, FALSE);
283 g_return_val_if_fail (process->priv->command != NULL, FALSE);
284 g_return_val_if_fail (process->priv->pid == 0, FALSE);
286 /* Create the log file owned by the target user */
287 if (process->priv->log_file)
289 gint fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
291 if (getuid () == 0 && chown (process->priv->log_file, user_get_uid (process->priv->user), user_get_gid (process->priv->user)) != 0)
292 g_warning ("Failed to set process log file ownership: %s", strerror (errno));
298 g_warning ("Failed to fork: %s", strerror (errno));
305 string = g_string_new ("");
306 g_hash_table_iter_init (&iter, process->priv->env);
307 while (g_hash_table_iter_next (&iter, &key, &value))
308 g_string_append_printf (string, "%s=%s ", (gchar *)key, (gchar *)value);
309 g_string_append (string, process->priv->command);
310 g_debug ("Launching process %d: %s", pid, string->str);
311 g_string_free (string, TRUE);
313 process->priv->pid = pid;
315 g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
316 g_child_watch_add (process->priv->pid, process_watch_cb, process);
318 g_signal_emit (process, signals[STARTED], 0);
324 process_get_is_running (Process *process)
326 g_return_val_if_fail (process != NULL, FALSE);
327 return process->priv->pid != 0;
331 process_get_pid (Process *process)
333 g_return_val_if_fail (process != NULL, 0);
334 return process->priv->pid;
338 process_signal (Process *process, int signum)
340 g_return_if_fail (process != NULL);
342 if (process->priv->pid == 0)
345 g_debug ("Sending signal %d to process %d", signum, process->priv->pid);
347 if (kill (process->priv->pid, signum) < 0)
348 g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
352 quit_timeout_cb (Process *process)
354 process->priv->quit_timeout = 0;
355 process_signal (process, SIGKILL);
360 process_stop (Process *process)
362 /* Send SIGTERM, and then SIGKILL if no response */
363 process->priv->quit_timeout = g_timeout_add (5000, (GSourceFunc) quit_timeout_cb, process);
364 process_signal (process, SIGTERM);
368 process_init (Process *process)
370 process->priv = G_TYPE_INSTANCE_GET_PRIVATE (process, PROCESS_TYPE, ProcessPrivate);
371 process->priv->env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
375 process_stopped (Process *process)
380 process_finalize (GObject *object)
384 self = PROCESS (object);
386 if (self->priv->pid > 0)
387 g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
389 g_free (self->priv->command);
390 g_free (self->priv->working_directory);
391 g_free (self->priv->log_file);
392 if (self->priv->user)
393 g_object_unref (self->priv->user);
396 kill (self->priv->pid, SIGTERM);
398 g_hash_table_unref (self->priv->env);
400 G_OBJECT_CLASS (process_parent_class)->finalize (object);
404 signal_cb (int signum, siginfo_t *info, void *data)
406 /* NOTE: Using g_printerr as can't call g_warning from a signal callback */
407 if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
408 write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
409 g_printerr ("Failed to write to signal pipe: %s", strerror (errno));
413 handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
419 if (read (signal_pipe[0], &signo, sizeof (int)) < 0 ||
420 read (signal_pipe[0], &pid, sizeof (pid_t)) < 0)
422 g_warning ("Error reading from signal pipe: %s", strerror (errno));
426 g_debug ("Got signal %d from process %d", signo, pid);
428 process = g_hash_table_lookup (processes, GINT_TO_POINTER (pid));
430 process = process_get_current ();
432 g_signal_emit (process, signals[GOT_SIGNAL], 0, signo);
438 process_class_init (ProcessClass *klass)
440 GObjectClass *object_class = G_OBJECT_CLASS (klass);
441 struct sigaction action;
443 klass->run = process_run;
444 klass->stopped = process_stopped;
445 object_class->finalize = process_finalize;
447 g_type_class_add_private (klass, sizeof (ProcessPrivate));
451 G_TYPE_FROM_CLASS (klass),
453 G_STRUCT_OFFSET (ProcessClass, run),
455 g_cclosure_marshal_VOID__VOID,
458 g_signal_new ("started",
459 G_TYPE_FROM_CLASS (klass),
461 G_STRUCT_OFFSET (ProcessClass, started),
463 g_cclosure_marshal_VOID__VOID,
466 g_signal_new ("got-data",
467 G_TYPE_FROM_CLASS (klass),
469 G_STRUCT_OFFSET (ProcessClass, got_data),
471 g_cclosure_marshal_VOID__VOID,
473 signals[GOT_SIGNAL] =
474 g_signal_new ("got-signal",
475 G_TYPE_FROM_CLASS (klass),
477 G_STRUCT_OFFSET (ProcessClass, got_signal),
479 g_cclosure_marshal_VOID__INT,
480 G_TYPE_NONE, 1, G_TYPE_INT);
482 g_signal_new ("exited",
483 G_TYPE_FROM_CLASS (klass),
485 G_STRUCT_OFFSET (ProcessClass, exited),
487 g_cclosure_marshal_VOID__INT,
488 G_TYPE_NONE, 1, G_TYPE_INT);
489 signals[TERMINATED] =
490 g_signal_new ("terminated",
491 G_TYPE_FROM_CLASS (klass),
493 G_STRUCT_OFFSET (ProcessClass, terminated),
495 g_cclosure_marshal_VOID__INT,
496 G_TYPE_NONE, 1, G_TYPE_INT);
498 g_signal_new ("stopped",
499 G_TYPE_FROM_CLASS (klass),
501 G_STRUCT_OFFSET (ProcessClass, stopped),
503 g_cclosure_marshal_VOID__VOID,
506 /* Catch signals and feed them to the main loop via a pipe */
507 processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
508 if (pipe (signal_pipe) != 0)
509 g_critical ("Failed to create signal pipe");
510 g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
511 action.sa_sigaction = signal_cb;
512 sigemptyset (&action.sa_mask);
513 action.sa_flags = SA_SIGINFO;
514 sigaction (SIGTERM, &action, NULL);
515 sigaction (SIGINT, &action, NULL);
516 sigaction (SIGHUP, &action, NULL);
517 sigaction (SIGUSR1, &action, NULL);
518 sigaction (SIGUSR2, &action, NULL);