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