]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/process.c
Re-enable SIGPIPE for children so they have default behaviour
[sojka/lightdm.git] / src / process.c
1 /*
2  * Copyright (C) 2010-2011 Robert Ancell.
3  * Author: Robert Ancell <robert.ancell@canonical.com>
4  *
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
9  * license.
10  */
11
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include <sys/wait.h>
17 #include <fcntl.h>
18 #include <signal.h>
19 #include <grp.h>
20 #include <config.h>
21
22 #include "log-file.h"
23 #include "process.h"
24
25 enum {
26     GOT_DATA,
27     GOT_SIGNAL,
28     STOPPED,
29     LAST_SIGNAL
30 };
31 static guint signals[LAST_SIGNAL] = { 0 };
32
33 struct ProcessPrivate
34 {
35     /* Function to run inside subprocess before exec */
36     ProcessRunFunc run_func;
37     gpointer run_func_data;
38
39     /* File to log to */
40     gchar *log_file;
41     gboolean log_stdout;
42     LogMode log_mode;
43
44     /* Command to run */
45     gchar *command;
46
47     /* TRUE to clear the environment in this process */
48     gboolean clear_environment;
49
50     /* Environment variables to set */
51     GHashTable *env;
52
53     /* Process ID */
54     GPid pid;
55
56     /* Exit status of process */
57     int exit_status;
58
59     /* TRUE if stopping this process (waiting for child process to stop) */
60     gboolean stopping;
61
62     /* Timeout waiting for process to quit */
63     guint quit_timeout;
64
65     /* Watch on process */
66     guint watch;
67 };
68
69 G_DEFINE_TYPE (Process, process, G_TYPE_OBJECT);
70
71 static Process *current_process = NULL;
72 static GHashTable *processes = NULL;
73 static pid_t signal_pid;
74 static int signal_pipe[2];
75
76 Process *
77 process_get_current (void)
78 {
79     if (current_process)
80         return current_process;
81
82     current_process = process_new (NULL, NULL);
83     current_process->priv->pid = getpid ();
84
85     return current_process;
86 }
87
88 Process *
89 process_new (ProcessRunFunc run_func, gpointer run_func_data)
90 {
91     Process *process = g_object_new (PROCESS_TYPE, NULL);
92     process->priv->run_func = run_func;
93     process->priv->run_func_data = run_func_data;
94     process->priv->log_mode = LOG_MODE_INVALID;
95     return process;
96 }
97
98 void
99 process_set_log_file (Process *process, const gchar *path, gboolean log_stdout, LogMode log_mode)
100 {
101     g_return_if_fail (process != NULL);
102     g_free (process->priv->log_file);
103     process->priv->log_file = g_strdup (path);
104     process->priv->log_stdout = log_stdout;
105     process->priv->log_mode = log_mode;
106 }
107
108 void
109 process_set_clear_environment (Process *process, gboolean clear_environment)
110 {
111     g_return_if_fail (process != NULL);
112     process->priv->clear_environment = clear_environment;
113 }
114
115 gboolean
116 process_get_clear_environment (Process *process)
117 {
118     g_return_val_if_fail (process != NULL, FALSE);
119     return process->priv->clear_environment;
120 }
121
122 void
123 process_set_env (Process *process, const gchar *name, const gchar *value)
124 {
125     g_return_if_fail (process != NULL);
126     g_return_if_fail (name != NULL);
127     g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));
128 }
129
130 const gchar *
131 process_get_env (Process *process, const gchar *name)
132 {
133     g_return_val_if_fail (process != NULL, NULL);
134     g_return_val_if_fail (name != NULL, NULL);
135     return g_hash_table_lookup (process->priv->env, name);
136 }
137
138 void
139 process_set_command (Process *process, const gchar *command)
140 {
141     g_return_if_fail (process != NULL);
142
143     g_free (process->priv->command);
144     process->priv->command = g_strdup (command);
145 }
146
147 const gchar *
148 process_get_command (Process *process)
149 {
150     g_return_val_if_fail (process != NULL, NULL);
151     return process->priv->command;
152 }
153
154 static void
155 process_watch_cb (GPid pid, gint status, gpointer data)
156 {
157     Process *process = data;
158
159     process->priv->watch = 0;
160     process->priv->exit_status = status;
161
162     if (WIFEXITED (status))
163         g_debug ("Process %d exited with return value %d", pid, WEXITSTATUS (status));
164     else if (WIFSIGNALED (status))
165         g_debug ("Process %d terminated with signal %d", pid, WTERMSIG (status));
166
167     if (process->priv->quit_timeout)
168         g_source_remove (process->priv->quit_timeout);
169     process->priv->quit_timeout = 0;
170     process->priv->pid = 0;
171     g_hash_table_remove (processes, GINT_TO_POINTER (pid));
172
173     g_signal_emit (process, signals[STOPPED], 0);
174 }
175
176 gboolean
177 process_start (Process *process, gboolean block)
178 {
179     gint argc;
180     gchar **argv;
181     gchar **env_keys, **env_values;
182     guint i, env_length;
183     GList *keys, *link;
184     pid_t pid;
185     int log_fd = -1;
186     GError *error = NULL;
187
188     g_return_val_if_fail (process != NULL, FALSE);
189     g_return_val_if_fail (process->priv->command != NULL, FALSE);
190     g_return_val_if_fail (process->priv->pid == 0, FALSE);
191
192     if (!g_shell_parse_argv (process->priv->command, &argc, &argv, &error))
193     {
194         g_warning ("Error parsing command %s: %s", process->priv->command, error->message);
195         return FALSE;
196     }
197
198     if (process->priv->log_file)
199         log_fd = log_file_open (process->priv->log_file, process->priv->log_mode);
200
201     /* Work out variables to set */
202     env_length = g_hash_table_size (process->priv->env);
203     env_keys = g_malloc (sizeof (gchar *) * env_length);
204     env_values = g_malloc (sizeof (gchar *) * env_length);
205     keys = g_hash_table_get_keys (process->priv->env);
206     for (i = 0, link = keys; i < env_length; i++, link = link->next)
207     {
208         env_keys[i] = link->data;
209         env_values[i] = g_hash_table_lookup (process->priv->env, env_keys[i]);
210     }
211     g_list_free (keys);
212
213     pid = fork ();
214     if (pid == 0)
215     {
216         /* Do custom setup */
217         if (process->priv->run_func)
218             process->priv->run_func (process, process->priv->run_func_data);
219
220         /* Redirect output to logfile */
221         if (log_fd >= 0)
222         {
223              if (process->priv->log_stdout)
224                  dup2 (log_fd, STDOUT_FILENO);
225              dup2 (log_fd, STDERR_FILENO);
226              close (log_fd);
227         }
228
229         /* Set environment */
230         if (process->priv->clear_environment)
231 #ifdef HAVE_CLEARENV
232             clearenv ();
233 #else
234             environ = NULL;
235 #endif
236         for (i = 0; i < env_length; i++)
237             setenv (env_keys[i], env_values[i], TRUE);
238
239         /* Reset SIGPIPE handler so the child has default behaviour (we disabled it at LightDM start) */
240         signal (SIGPIPE, SIG_DFL);
241
242         execvp (argv[0], argv);
243         _exit (EXIT_FAILURE);
244     }
245
246     close (log_fd);
247     g_strfreev (argv);
248     g_free (env_keys);
249     g_free (env_values);
250
251     if (pid < 0)
252     {
253         g_warning ("Failed to fork: %s", strerror (errno));
254         return FALSE;
255     }
256
257     g_debug ("Launching process %d: %s", pid, process->priv->command);
258
259     process->priv->pid = pid;
260
261     if (block)
262     {
263         int exit_status;
264         waitpid (process->priv->pid, &exit_status, 0);
265         process_watch_cb (process->priv->pid, exit_status, process);
266     }
267     else
268     {
269         g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
270         process->priv->watch = g_child_watch_add (process->priv->pid, process_watch_cb, process);
271     }
272
273     return TRUE;
274 }
275
276 gboolean
277 process_get_is_running (Process *process)
278 {
279     g_return_val_if_fail (process != NULL, FALSE);
280     return process->priv->pid != 0;
281 }
282
283 GPid
284 process_get_pid (Process *process)
285 {
286     g_return_val_if_fail (process != NULL, 0);
287     return process->priv->pid;
288 }
289
290 void
291 process_signal (Process *process, int signum)
292 {
293     g_return_if_fail (process != NULL);
294
295     if (process->priv->pid == 0)
296         return;
297
298     g_debug ("Sending signal %d to process %d", signum, process->priv->pid);
299
300     if (kill (process->priv->pid, signum) < 0)
301     {
302         /* Ignore ESRCH, we will pick that up in our wait */
303         if (errno != ESRCH)
304             g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
305     }
306 }
307
308 static gboolean
309 quit_timeout_cb (Process *process)
310 {
311     process->priv->quit_timeout = 0;
312     process_signal (process, SIGKILL);
313     return FALSE;
314 }
315
316 void
317 process_stop (Process *process)
318 {
319     g_return_if_fail (process != NULL);
320
321     if (process->priv->stopping)
322         return;
323     process->priv->stopping = TRUE;
324
325     /* If already stopped then we're done! */
326     if (process->priv->pid == 0)
327         return;
328
329     /* Send SIGTERM, and then SIGKILL if no response */
330     process->priv->quit_timeout = g_timeout_add (5000, (GSourceFunc) quit_timeout_cb, process);
331     process_signal (process, SIGTERM);
332 }
333
334 int
335 process_get_exit_status (Process *process)
336 {
337     g_return_val_if_fail (process != NULL, -1);
338     return process->priv->exit_status;
339 }
340
341 static void
342 process_init (Process *process)
343 {
344     process->priv = G_TYPE_INSTANCE_GET_PRIVATE (process, PROCESS_TYPE, ProcessPrivate);
345     process->priv->env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
346 }
347
348 static void
349 process_stopped (Process *process)
350 {
351 }
352
353 static void
354 process_finalize (GObject *object)
355 {
356     Process *self = PROCESS (object);
357
358     if (self->priv->pid > 0)
359         g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
360
361     g_free (self->priv->log_file);
362     g_free (self->priv->command);
363     g_hash_table_unref (self->priv->env);
364     if (self->priv->quit_timeout)
365         g_source_remove (self->priv->quit_timeout);
366     if (self->priv->watch)
367         g_source_remove (self->priv->watch);
368
369     if (self->priv->pid)
370         kill (self->priv->pid, SIGTERM);
371
372     G_OBJECT_CLASS (process_parent_class)->finalize (object);
373 }
374
375 static void
376 signal_cb (int signum, siginfo_t *info, void *data)
377 {
378     /* Check if we are from a forked process that hasn't updated the signal handlers or execed.
379        If so, then we should just quit */
380     if (getpid () != signal_pid)
381         _exit (EXIT_SUCCESS);
382
383     /* Write signal to main thread, if something goes wrong just close the pipe so it is detected on the other end */
384     if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
385         write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
386         close (signal_pipe[1]);
387 }
388
389 static gboolean
390 handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
391 {
392     int signo;
393     pid_t pid;
394     Process *process;
395
396     errno = 0;
397     if (read (signal_pipe[0], &signo, sizeof (int)) != sizeof (int) ||
398         read (signal_pipe[0], &pid, sizeof (pid_t)) != sizeof (pid_t))
399     {
400         g_warning ("Error reading from signal pipe: %s", strerror (errno));
401         return FALSE;
402     }
403
404     g_debug ("Got signal %d from process %d", signo, pid);
405
406     process = g_hash_table_lookup (processes, GINT_TO_POINTER (pid));
407     if (process == NULL)
408         process = process_get_current ();
409     if (process)
410         g_signal_emit (process, signals[GOT_SIGNAL], 0, signo);
411
412     return TRUE;
413 }
414
415 static void
416 process_class_init (ProcessClass *klass)
417 {
418     GObjectClass *object_class = G_OBJECT_CLASS (klass);
419     struct sigaction action;
420
421     klass->stopped = process_stopped;
422     object_class->finalize = process_finalize;
423
424     g_type_class_add_private (klass, sizeof (ProcessPrivate));
425
426     signals[GOT_DATA] =
427         g_signal_new (PROCESS_SIGNAL_GOT_DATA,
428                       G_TYPE_FROM_CLASS (klass),
429                       G_SIGNAL_RUN_LAST,
430                       G_STRUCT_OFFSET (ProcessClass, got_data),
431                       NULL, NULL,
432                       NULL,
433                       G_TYPE_NONE, 0);
434     signals[GOT_SIGNAL] =
435         g_signal_new (PROCESS_SIGNAL_GOT_SIGNAL,
436                       G_TYPE_FROM_CLASS (klass),
437                       G_SIGNAL_RUN_LAST,
438                       G_STRUCT_OFFSET (ProcessClass, got_signal),
439                       NULL, NULL,
440                       NULL,
441                       G_TYPE_NONE, 1, G_TYPE_INT);
442     signals[STOPPED] =
443         g_signal_new (PROCESS_SIGNAL_STOPPED,
444                       G_TYPE_FROM_CLASS (klass),
445                       G_SIGNAL_RUN_LAST,
446                       G_STRUCT_OFFSET (ProcessClass, stopped),
447                       NULL, NULL,
448                       NULL,
449                       G_TYPE_NONE, 0);
450
451     /* Catch signals and feed them to the main loop via a pipe */
452     processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
453     signal_pid = getpid ();
454     if (pipe (signal_pipe) != 0)
455         g_critical ("Failed to create signal pipe");
456     fcntl (signal_pipe[0], F_SETFD, FD_CLOEXEC);
457     fcntl (signal_pipe[1], F_SETFD, FD_CLOEXEC);
458     g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
459     action.sa_sigaction = signal_cb;
460     sigemptyset (&action.sa_mask);
461     action.sa_flags = SA_SIGINFO;
462     sigaction (SIGTERM, &action, NULL);
463     sigaction (SIGINT, &action, NULL);
464     sigaction (SIGHUP, &action, NULL);
465     sigaction (SIGUSR1, &action, NULL);
466     sigaction (SIGUSR2, &action, NULL);
467 }