/*
* Copyright (C) 2010-2011 Robert Ancell.
* Author: Robert Ancell <robert.ancell@canonical.com>
- *
+ *
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
#include <fcntl.h>
#include <signal.h>
#include <grp.h>
-#include <glib/gstdio.h>
+#include <config.h>
+#include "log-file.h"
#include "process.h"
enum {
- RUN,
- STARTED,
GOT_DATA,
- GOT_SIGNAL,
+ GOT_SIGNAL,
STOPPED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
struct ProcessPrivate
-{
- /* Environment variables */
- GHashTable *env;
+{
+ /* Function to run inside subprocess before exec */
+ ProcessRunFunc run_func;
+ gpointer run_func_data;
+
+ /* File to log to */
+ gchar *log_file;
+ gboolean log_stdout;
+ LogMode log_mode;
/* Command to run */
gchar *command;
- /* Working directory */
- gchar *working_directory;
+ /* TRUE to clear the environment in this process */
+ gboolean clear_environment;
- /* User to run as */
- User *user;
+ /* Environment variables to set */
+ GHashTable *env;
- /* Path of file to log to */
- gchar *log_file;
-
/* Process ID */
GPid pid;
-
+
/* Exit status of process */
int exit_status;
+ /* TRUE if stopping this process (waiting for child process to stop) */
+ gboolean stopping;
+
/* Timeout waiting for process to quit */
guint quit_timeout;
+
+ /* Watch on process */
+ guint watch;
};
G_DEFINE_TYPE (Process, process, G_TYPE_OBJECT);
static Process *current_process = NULL;
static GHashTable *processes = NULL;
+static pid_t signal_pid;
static int signal_pipe[2];
Process *
if (current_process)
return current_process;
- current_process = process_new ();
+ current_process = process_new (NULL, NULL);
current_process->priv->pid = getpid ();
return current_process;
}
Process *
-process_new (void)
+process_new (ProcessRunFunc run_func, gpointer run_func_data)
{
- return g_object_new (PROCESS_TYPE, NULL);
+ Process *process = g_object_new (PROCESS_TYPE, NULL);
+ process->priv->run_func = run_func;
+ process->priv->run_func_data = run_func_data;
+ process->priv->log_mode = LOG_MODE_INVALID;
+ return process;
}
void
-process_set_command (Process *process, const gchar *command)
+process_set_log_file (Process *process, const gchar *path, gboolean log_stdout, LogMode log_mode)
{
g_return_if_fail (process != NULL);
-
- g_free (process->priv->command);
- process->priv->command = g_strdup (command);
-}
-
-const gchar *
-process_get_command (Process *process)
-{
- g_return_val_if_fail (process != NULL, NULL);
- return process->priv->command;
+ g_free (process->priv->log_file);
+ process->priv->log_file = g_strdup (path);
+ process->priv->log_stdout = log_stdout;
+ process->priv->log_mode = log_mode;
}
void
-process_set_log_file (Process *process, const gchar *log_file)
+process_set_clear_environment (Process *process, gboolean clear_environment)
{
g_return_if_fail (process != NULL);
-
- g_free (process->priv->log_file);
- process->priv->log_file = g_strdup (log_file);
+ process->priv->clear_environment = clear_environment;
}
-const gchar *
-process_get_log_file (Process *process)
+gboolean
+process_get_clear_environment (Process *process)
{
- g_return_val_if_fail (process != NULL, NULL);
- return process->priv->log_file;
+ g_return_val_if_fail (process != NULL, FALSE);
+ return process->priv->clear_environment;
}
void
-process_set_working_directory (Process *process, const gchar *working_directory)
+process_set_env (Process *process, const gchar *name, const gchar *value)
{
g_return_if_fail (process != NULL);
-
- g_free (process->priv->working_directory);
- process->priv->working_directory = g_strdup (working_directory);
+ g_return_if_fail (name != NULL);
+ g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));
}
const gchar *
-process_get_working_directory (Process *process)
+process_get_env (Process *process, const gchar *name)
{
g_return_val_if_fail (process != NULL, NULL);
- return process->priv->working_directory;
+ g_return_val_if_fail (name != NULL, NULL);
+ return g_hash_table_lookup (process->priv->env, name);
}
void
-process_set_user (Process *process, User *user)
+process_set_command (Process *process, const gchar *command)
{
g_return_if_fail (process != NULL);
- if (process->priv->user)
- g_object_unref (process->priv->user);
- process->priv->user = g_object_ref (user);
-}
-
-User *
-process_get_user (Process *process)
-{
- g_return_val_if_fail (process != NULL, NULL);
- return process->priv->user;
-}
-
-void
-process_set_env (Process *process, const gchar *name, const gchar *value)
-{
- g_return_if_fail (process != NULL);
- g_return_if_fail (name != NULL);
- g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));
+ g_free (process->priv->command);
+ process->priv->command = g_strdup (command);
}
const gchar *
-process_get_env (Process *process, const gchar *name)
+process_get_command (Process *process)
{
- g_return_val_if_fail (process != NULL, FALSE);
- return g_hash_table_lookup (process->priv->env, name);
+ g_return_val_if_fail (process != NULL, NULL);
+ return process->priv->command;
}
static void
{
Process *process = data;
+ process->priv->watch = 0;
process->priv->exit_status = status;
if (WIFEXITED (status))
if (process->priv->quit_timeout)
g_source_remove (process->priv->quit_timeout);
- process->priv->quit_timeout = 0;
+ process->priv->quit_timeout = 0;
process->priv->pid = 0;
g_hash_table_remove (processes, GINT_TO_POINTER (pid));
g_signal_emit (process, signals[STOPPED], 0);
}
-static void
-run (Process *process)
-{
- GHashTableIter iter;
- gpointer key, value;
-
- /* FIXME: Close existing file descriptors */
-
- /* Set environment */
- clearenv ();
- g_hash_table_iter_init (&iter, process->priv->env);
- while (g_hash_table_iter_next (&iter, &key, &value))
- g_setenv ((gchar *)key, (gchar *)value, TRUE);
-
- /* Make this process its own session */
- if (setsid () < 0)
- g_warning ("Failed to make process a new session: %s", strerror (errno));
-
- if (process->priv->user)
- {
- if (getuid () == 0)
- {
- if (initgroups (user_get_name (process->priv->user), user_get_gid (process->priv->user)) < 0)
- {
- g_warning ("Failed to initialize supplementary groups for %s: %s", user_get_name (process->priv->user), strerror (errno));
- _exit (EXIT_FAILURE);
- }
- }
-
- if (chdir (user_get_home_directory (process->priv->user)) != 0)
- {
- g_warning ("Failed to change to home directory %s: %s", user_get_home_directory (process->priv->user), strerror (errno));
- _exit (EXIT_FAILURE);
- }
- }
-
- /* Redirect output to logfile */
- if (process->priv->log_file)
- {
- int fd;
-
- fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
- if (fd < 0)
- g_warning ("Failed to open log file %s: %s", process->priv->log_file, g_strerror (errno));
- else
- {
- dup2 (fd, STDOUT_FILENO);
- dup2 (fd, STDERR_FILENO);
- close (fd);
- }
- }
-
- g_signal_emit (process, signals[RUN], 0);
-}
-
-static void
-process_run (Process *process)
+gboolean
+process_start (Process *process, gboolean block)
{
gint argc;
gchar **argv;
+ gchar **env_keys, **env_values;
+ guint i, env_length;
+ GList *keys, *link;
+ pid_t pid;
+ int log_fd = -1;
GError *error = NULL;
+ g_return_val_if_fail (process != NULL, FALSE);
+ g_return_val_if_fail (process->priv->command != NULL, FALSE);
+ g_return_val_if_fail (process->priv->pid == 0, FALSE);
+
if (!g_shell_parse_argv (process->priv->command, &argc, &argv, &error))
{
g_warning ("Error parsing command %s: %s", process->priv->command, error->message);
- _exit (EXIT_FAILURE);
+ return FALSE;
}
- /* Drop privileges */
- if (process->priv->user && getuid () == 0)
+ if (process->priv->log_file)
+ log_fd = log_file_open (process->priv->log_file, process->priv->log_mode);
+
+ /* Work out variables to set */
+ env_length = g_hash_table_size (process->priv->env);
+ env_keys = g_malloc (sizeof (gchar *) * env_length);
+ env_values = g_malloc (sizeof (gchar *) * env_length);
+ keys = g_hash_table_get_keys (process->priv->env);
+ for (i = 0, link = keys; i < env_length; i++, link = link->next)
{
- if (setgid (user_get_gid (process->priv->user)) != 0)
- {
- g_warning ("Failed to set group ID to %d: %s", user_get_gid (process->priv->user), strerror (errno));
- _exit (EXIT_FAILURE);
- }
+ env_keys[i] = link->data;
+ env_values[i] = g_hash_table_lookup (process->priv->env, env_keys[i]);
+ }
+ g_list_free (keys);
- if (setuid (user_get_uid (process->priv->user)) != 0)
+ pid = fork ();
+ if (pid == 0)
+ {
+ /* Do custom setup */
+ if (process->priv->run_func)
+ process->priv->run_func (process, process->priv->run_func_data);
+
+ /* Redirect output to logfile */
+ if (log_fd >= 0)
{
- g_warning ("Failed to set user ID to %d: %s", user_get_uid (process->priv->user), strerror (errno));
- _exit (EXIT_FAILURE);
+ if (process->priv->log_stdout)
+ dup2 (log_fd, STDOUT_FILENO);
+ dup2 (log_fd, STDERR_FILENO);
+ close (log_fd);
}
- }
-
- execvp (argv[0], argv);
- g_warning ("Error executing child process %s: %s", argv[0], g_strerror (errno));
- _exit (EXIT_FAILURE);
-}
+ /* Set environment */
+ if (process->priv->clear_environment)
+#ifdef HAVE_CLEARENV
+ clearenv ();
+#else
+ environ = NULL;
+#endif
+ for (i = 0; i < env_length; i++)
+ setenv (env_keys[i], env_values[i], TRUE);
-gboolean
-process_start (Process *process)
-{
- GString *string;
- gpointer key, value;
- GHashTableIter iter;
- pid_t pid;
-
- g_return_val_if_fail (process != NULL, FALSE);
- g_return_val_if_fail (process->priv->command != NULL, FALSE);
- g_return_val_if_fail (process->priv->pid == 0, FALSE);
+ /* Reset SIGPIPE handler so the child has default behaviour (we disabled it at LightDM start) */
+ signal (SIGPIPE, SIG_DFL);
- /* Create the log file owned by the target user */
- if (process->priv->log_file)
- {
- gint fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
- close (fd);
- if (getuid () == 0 && chown (process->priv->log_file, user_get_uid (process->priv->user), user_get_gid (process->priv->user)) != 0)
- g_warning ("Failed to set process log file ownership: %s", strerror (errno));
+ execvp (argv[0], argv);
+ _exit (EXIT_FAILURE);
}
- pid = fork ();
+ close (log_fd);
+ g_strfreev (argv);
+ g_free (env_keys);
+ g_free (env_values);
+
if (pid < 0)
{
g_warning ("Failed to fork: %s", strerror (errno));
return FALSE;
}
- if (pid == 0)
- run (process);
-
- string = g_string_new ("");
- g_hash_table_iter_init (&iter, process->priv->env);
- while (g_hash_table_iter_next (&iter, &key, &value))
- g_string_append_printf (string, "%s=%s ", (gchar *)key, (gchar *)value);
- g_string_append (string, process->priv->command);
- g_debug ("Launching process %d: %s", pid, string->str);
- g_string_free (string, TRUE);
+ g_debug ("Launching process %d: %s", pid, process->priv->command);
process->priv->pid = pid;
- g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
- g_child_watch_add (process->priv->pid, process_watch_cb, process);
-
- g_signal_emit (process, signals[STARTED], 0);
+ if (block)
+ {
+ int exit_status;
+ waitpid (process->priv->pid, &exit_status, 0);
+ process_watch_cb (process->priv->pid, exit_status, process);
+ }
+ else
+ {
+ g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
+ process->priv->watch = g_child_watch_add (process->priv->pid, process_watch_cb, process);
+ }
return TRUE;
}
g_debug ("Sending signal %d to process %d", signum, process->priv->pid);
if (kill (process->priv->pid, signum) < 0)
- g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
+ {
+ /* Ignore ESRCH, we will pick that up in our wait */
+ if (errno != ESRCH)
+ g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
+ }
}
static gboolean
{
g_return_if_fail (process != NULL);
+ if (process->priv->stopping)
+ return;
+ process->priv->stopping = TRUE;
+
+ /* If already stopped then we're done! */
+ if (process->priv->pid == 0)
+ return;
+
/* Send SIGTERM, and then SIGKILL if no response */
process->priv->quit_timeout = g_timeout_add (5000, (GSourceFunc) quit_timeout_cb, process);
process_signal (process, SIGTERM);
}
-void
-process_wait (Process *process)
-{
- int exit_status;
-
- g_return_if_fail (process != NULL);
-
- waitpid (process->priv->pid, &exit_status, 0);
- process_watch_cb (process->priv->pid, exit_status, process);
-}
-
int
process_get_exit_status (Process *process)
{
static void
process_finalize (GObject *object)
{
- Process *self;
-
- self = PROCESS (object);
+ Process *self = PROCESS (object);
if (self->priv->pid > 0)
g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
- g_free (self->priv->command);
- g_free (self->priv->working_directory);
g_free (self->priv->log_file);
- if (self->priv->user)
- g_object_unref (self->priv->user);
+ g_free (self->priv->command);
+ g_hash_table_unref (self->priv->env);
+ if (self->priv->quit_timeout)
+ g_source_remove (self->priv->quit_timeout);
+ if (self->priv->watch)
+ g_source_remove (self->priv->watch);
if (self->priv->pid)
kill (self->priv->pid, SIGTERM);
- g_hash_table_unref (self->priv->env);
-
G_OBJECT_CLASS (process_parent_class)->finalize (object);
}
static void
signal_cb (int signum, siginfo_t *info, void *data)
{
- /* NOTE: Using g_printerr as can't call g_warning from a signal callback */
+ /* Check if we are from a forked process that hasn't updated the signal handlers or execed.
+ If so, then we should just quit */
+ if (getpid () != signal_pid)
+ _exit (EXIT_SUCCESS);
+
+ /* Write signal to main thread, if something goes wrong just close the pipe so it is detected on the other end */
if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
- g_printerr ("Failed to write to signal pipe: %s", strerror (errno));
+ close (signal_pipe[1]);
}
static gboolean
pid_t pid;
Process *process;
- if (read (signal_pipe[0], &signo, sizeof (int)) < 0 ||
- read (signal_pipe[0], &pid, sizeof (pid_t)) < 0)
+ errno = 0;
+ if (read (signal_pipe[0], &signo, sizeof (int)) != sizeof (int) ||
+ read (signal_pipe[0], &pid, sizeof (pid_t)) != sizeof (pid_t))
{
g_warning ("Error reading from signal pipe: %s", strerror (errno));
- return TRUE;
+ return FALSE;
}
g_debug ("Got signal %d from process %d", signo, pid);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
struct sigaction action;
- klass->run = process_run;
klass->stopped = process_stopped;
- object_class->finalize = process_finalize;
+ object_class->finalize = process_finalize;
g_type_class_add_private (klass, sizeof (ProcessPrivate));
- signals[RUN] =
- g_signal_new ("run",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (ProcessClass, run),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
- signals[STARTED] =
- g_signal_new ("started",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (ProcessClass, started),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
signals[GOT_DATA] =
- g_signal_new ("got-data",
+ g_signal_new (PROCESS_SIGNAL_GOT_DATA,
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ProcessClass, got_data),
NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
+ NULL,
+ G_TYPE_NONE, 0);
signals[GOT_SIGNAL] =
- g_signal_new ("got-signal",
+ g_signal_new (PROCESS_SIGNAL_GOT_SIGNAL,
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ProcessClass, got_signal),
NULL, NULL,
- g_cclosure_marshal_VOID__INT,
+ NULL,
G_TYPE_NONE, 1, G_TYPE_INT);
signals[STOPPED] =
- g_signal_new ("stopped",
+ g_signal_new (PROCESS_SIGNAL_STOPPED,
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ProcessClass, stopped),
NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
+ NULL,
G_TYPE_NONE, 0);
/* Catch signals and feed them to the main loop via a pipe */
processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
+ signal_pid = getpid ();
if (pipe (signal_pipe) != 0)
g_critical ("Failed to create signal pipe");
+ fcntl (signal_pipe[0], F_SETFD, FD_CLOEXEC);
+ fcntl (signal_pipe[1], F_SETFD, FD_CLOEXEC);
g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
action.sa_sigaction = signal_cb;
sigemptyset (&action.sa_mask);