]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blobdiff - src/process.c
Re-enable SIGPIPE for children so they have default behaviour
[sojka/lightdm.git] / src / process.c
index d6adc5b524264412623244ff56773580b5c719d9..75357db615b99466fe31b0988479cbfb8d102395 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * 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 *
@@ -70,96 +79,76 @@ process_get_current (void)
     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
@@ -167,6 +156,7 @@ process_watch_cb (GPid pid, gint status, gpointer data)
 {
     Process *process = data;
 
+    process->priv->watch = 0;
     process->priv->exit_status = status;
 
     if (WIFEXITED (status))
@@ -176,148 +166,109 @@ process_watch_cb (GPid pid, gint status, gpointer data)
 
     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;
 }
@@ -347,7 +298,11 @@ process_signal (Process *process, int signum)
     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
@@ -363,22 +318,19 @@ process_stop (Process *process)
 {
     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)
 {
@@ -401,34 +353,37 @@ process_stopped (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
@@ -438,11 +393,12 @@ handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
     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);
@@ -462,57 +418,43 @@ process_class_init (ProcessClass *klass)
     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);