]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/process.c
Fix autologin use case and add a test for it
[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     RUN,
27     GOT_DATA,
28     GOT_SIGNAL,  
29     STOPPED,
30     LAST_SIGNAL
31 };
32 static guint signals[LAST_SIGNAL] = { 0 };
33
34 struct ProcessPrivate
35 {  
36     /* Command to run */
37     gchar *command;
38   
39     /* TRUE to clear the environment in this process */
40     gboolean clear_environment;
41
42     /* Environment variables to set */
43     GHashTable *env;
44
45     /* Process ID */
46     GPid pid;
47   
48     /* Exit status of process */
49     int exit_status;
50
51     /* Timeout waiting for process to quit */
52     guint quit_timeout;
53
54     /* Watch on process */
55     guint watch;
56 };
57
58 G_DEFINE_TYPE (Process, process, G_TYPE_OBJECT);
59
60 static Process *current_process = NULL;
61 static GHashTable *processes = NULL;
62 static int signal_pipe[2];
63
64 Process *
65 process_get_current (void)
66 {
67     if (current_process)
68         return current_process;
69
70     current_process = process_new ();
71     current_process->priv->pid = getpid ();
72
73     return current_process;
74 }
75
76 Process *
77 process_new (void)
78 {
79     return g_object_new (PROCESS_TYPE, NULL);
80 }
81
82 void
83 process_set_clear_environment (Process *process, gboolean clear_environment)
84 {
85     g_return_if_fail (process != NULL);
86     process->priv->clear_environment = clear_environment;
87 }
88
89 gboolean
90 process_get_clear_environment (Process *process)
91 {
92     g_return_val_if_fail (process != NULL, FALSE);
93     return process->priv->clear_environment;
94 }
95
96 void
97 process_set_env (Process *process, const gchar *name, const gchar *value)
98 {
99     g_return_if_fail (process != NULL);
100     g_return_if_fail (name != NULL);
101     g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));  
102 }
103
104 const gchar *
105 process_get_env (Process *process, const gchar *name)
106 {
107     g_return_val_if_fail (process != NULL, NULL);
108     g_return_val_if_fail (name != NULL, NULL);
109     return g_hash_table_lookup (process->priv->env, name);
110 }
111
112 void
113 process_set_command (Process *process, const gchar *command)
114 {
115     g_return_if_fail (process != NULL);
116
117     g_free (process->priv->command);
118     process->priv->command = g_strdup (command);
119 }
120
121 const gchar *
122 process_get_command (Process *process)
123 {
124     g_return_val_if_fail (process != NULL, NULL);
125     return process->priv->command;
126 }
127
128 static void
129 process_watch_cb (GPid pid, gint status, gpointer data)
130 {
131     Process *process = data;
132
133     process->priv->exit_status = status;
134
135     if (WIFEXITED (status))
136         g_debug ("Process %d exited with return value %d", pid, WEXITSTATUS (status));
137     else if (WIFSIGNALED (status))
138         g_debug ("Process %d terminated with signal %d", pid, WTERMSIG (status));
139
140     if (process->priv->watch)
141         g_source_remove (process->priv->watch);
142     process->priv->watch = 0;
143
144     if (process->priv->quit_timeout)
145         g_source_remove (process->priv->quit_timeout);
146     process->priv->quit_timeout = 0;  
147     process->priv->pid = 0;
148     g_hash_table_remove (processes, GINT_TO_POINTER (pid));
149
150     g_signal_emit (process, signals[STOPPED], 0);
151 }
152
153 static void
154 process_run (Process *process)
155 {
156     gint argc;
157     gchar **argv;
158     GHashTableIter iter;
159     gpointer key, value;
160     GError *error = NULL;
161
162     if (!g_shell_parse_argv (process->priv->command, &argc, &argv, &error))
163     {
164         g_warning ("Error parsing command %s: %s", process->priv->command, error->message);
165         _exit (EXIT_FAILURE);
166     }
167
168     if (process->priv->clear_environment)
169 #ifdef HAVE_CLEARENV
170         clearenv ();
171 #else
172         environ = NULL;
173 #endif
174
175     g_hash_table_iter_init (&iter, process->priv->env);
176     while (g_hash_table_iter_next (&iter, &key, &value))
177         g_setenv ((gchar *)key, (gchar *)value, TRUE);
178   
179     execvp (argv[0], argv);
180
181     g_warning ("Error executing child process %s: %s", argv[0], g_strerror (errno));
182     _exit (EXIT_FAILURE);
183 }
184
185 gboolean
186 process_start (Process *process, gboolean block)
187 {
188     pid_t pid;
189
190     g_return_val_if_fail (process != NULL, FALSE);
191     g_return_val_if_fail (process->priv->command != NULL, FALSE);  
192     g_return_val_if_fail (process->priv->pid == 0, FALSE);
193
194     pid = fork ();
195     if (pid < 0)
196     {
197         g_warning ("Failed to fork: %s", strerror (errno));
198         return FALSE;
199     }
200
201     if (pid == 0)
202         g_signal_emit (process, signals[RUN], 0);
203
204     g_debug ("Launching process %d: %s", pid, process->priv->command);
205
206     process->priv->pid = pid;
207
208     if (block)
209     {
210         int exit_status;
211         waitpid (process->priv->pid, &exit_status, 0);
212         process_watch_cb (process->priv->pid, exit_status, process);
213     }  
214     else
215     {
216         g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
217         process->priv->watch = g_child_watch_add (process->priv->pid, process_watch_cb, process);
218     }
219
220     return TRUE;
221 }
222
223 gboolean
224 process_get_is_running (Process *process)
225 {
226     g_return_val_if_fail (process != NULL, FALSE);
227     return process->priv->pid != 0;
228 }
229
230 GPid
231 process_get_pid (Process *process)
232 {
233     g_return_val_if_fail (process != NULL, 0);
234     return process->priv->pid;
235 }
236
237 void
238 process_signal (Process *process, int signum)
239 {
240     g_return_if_fail (process != NULL);
241
242     if (process->priv->pid == 0)
243         return;
244
245     g_debug ("Sending signal %d to process %d", signum, process->priv->pid);
246
247     if (kill (process->priv->pid, signum) < 0)
248         g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
249 }
250
251 static gboolean
252 quit_timeout_cb (Process *process)
253 {
254     process->priv->quit_timeout = 0;
255     process_signal (process, SIGKILL);
256     return FALSE;
257 }
258
259 void
260 process_stop (Process *process)
261 {
262     g_return_if_fail (process != NULL);
263
264     /* Send SIGTERM, and then SIGKILL if no response */
265     process->priv->quit_timeout = g_timeout_add (5000, (GSourceFunc) quit_timeout_cb, process);
266     process_signal (process, SIGTERM);
267 }
268
269 int
270 process_get_exit_status (Process *process)
271 {
272     g_return_val_if_fail (process != NULL, -1);
273     return process->priv->exit_status;
274 }
275
276 static void
277 process_init (Process *process)
278 {
279     process->priv = G_TYPE_INSTANCE_GET_PRIVATE (process, PROCESS_TYPE, ProcessPrivate);
280     process->priv->env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
281 }
282
283 static void
284 process_stopped (Process *process)
285 {
286 }
287
288 static void
289 process_finalize (GObject *object)
290 {
291     Process *self;
292
293     self = PROCESS (object);
294
295     if (self->priv->pid > 0)
296         g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
297
298     g_free (self->priv->command);
299     g_hash_table_unref (self->priv->env);
300     if (self->priv->quit_timeout)
301         g_source_remove (self->priv->quit_timeout);
302     if (self->priv->watch)
303         g_source_remove (self->priv->watch);
304
305     if (self->priv->pid)
306         kill (self->priv->pid, SIGTERM);
307
308     G_OBJECT_CLASS (process_parent_class)->finalize (object);
309 }
310
311 static void
312 signal_cb (int signum, siginfo_t *info, void *data)
313 {
314     /* NOTE: Using g_printerr as can't call g_warning from a signal callback */
315     if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
316         write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
317         g_printerr ("Failed to write to signal pipe: %s", strerror (errno));
318 }
319
320 static gboolean
321 handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
322 {
323     int signo;
324     pid_t pid;
325     Process *process;
326
327     errno = 0;
328     if (read (signal_pipe[0], &signo, sizeof (int)) != sizeof (int) || 
329         read (signal_pipe[0], &pid, sizeof (pid_t)) != sizeof (pid_t))
330     {
331         g_warning ("Error reading from signal pipe: %s", strerror (errno));
332         return TRUE;
333     }
334
335     g_debug ("Got signal %d from process %d", signo, pid);
336
337     process = g_hash_table_lookup (processes, GINT_TO_POINTER (pid));
338     if (process == NULL)
339         process = process_get_current ();
340     if (process)
341         g_signal_emit (process, signals[GOT_SIGNAL], 0, signo);
342
343     return TRUE;
344 }
345
346 static void
347 process_class_init (ProcessClass *klass)
348 {
349     GObjectClass *object_class = G_OBJECT_CLASS (klass);
350     struct sigaction action;
351
352     klass->run = process_run;
353     klass->stopped = process_stopped;
354     object_class->finalize = process_finalize;  
355
356     g_type_class_add_private (klass, sizeof (ProcessPrivate));
357
358     signals[RUN] =
359         g_signal_new ("run",
360                       G_TYPE_FROM_CLASS (klass),
361                       G_SIGNAL_RUN_LAST,
362                       G_STRUCT_OFFSET (ProcessClass, run),
363                       NULL, NULL,
364                       NULL,
365                       G_TYPE_NONE, 0); 
366     signals[GOT_DATA] =
367         g_signal_new ("got-data",
368                       G_TYPE_FROM_CLASS (klass),
369                       G_SIGNAL_RUN_LAST,
370                       G_STRUCT_OFFSET (ProcessClass, got_data),
371                       NULL, NULL,
372                       NULL,
373                       G_TYPE_NONE, 0); 
374     signals[GOT_SIGNAL] =
375         g_signal_new ("got-signal",
376                       G_TYPE_FROM_CLASS (klass),
377                       G_SIGNAL_RUN_LAST,
378                       G_STRUCT_OFFSET (ProcessClass, got_signal),
379                       NULL, NULL,
380                       NULL,
381                       G_TYPE_NONE, 1, G_TYPE_INT);
382     signals[STOPPED] =
383         g_signal_new ("stopped",
384                       G_TYPE_FROM_CLASS (klass),
385                       G_SIGNAL_RUN_LAST,
386                       G_STRUCT_OFFSET (ProcessClass, stopped),
387                       NULL, NULL,
388                       NULL,
389                       G_TYPE_NONE, 0);
390
391     /* Catch signals and feed them to the main loop via a pipe */
392     processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
393     if (pipe (signal_pipe) != 0)
394         g_critical ("Failed to create signal pipe");
395     fcntl (signal_pipe[0], F_SETFD, FD_CLOEXEC);
396     fcntl (signal_pipe[1], F_SETFD, FD_CLOEXEC);
397     g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
398     action.sa_sigaction = signal_cb;
399     sigemptyset (&action.sa_mask);
400     action.sa_flags = SA_SIGINFO;
401     sigaction (SIGTERM, &action, NULL);
402     sigaction (SIGINT, &action, NULL);
403     sigaction (SIGHUP, &action, NULL);
404     sigaction (SIGUSR1, &action, NULL);
405     sigaction (SIGUSR2, &action, NULL);
406 }