]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/process.c
Add a gdmflexiserver binary that provides backwards compatibility with existing sessions
[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
22 #include "process.h"
23
24 enum {
25     STARTED,
26     GOT_DATA,
27     GOT_SIGNAL,  
28     EXITED,
29     TERMINATED,
30     STOPPED,
31     LAST_SIGNAL
32 };
33 static guint signals[LAST_SIGNAL] = { 0 };
34
35 struct ProcessPrivate
36 {  
37     /* Environment variables */
38     GHashTable *env;
39
40     /* User to run as */
41     User *user;
42
43     /* Path of file to log to */
44     gchar *log_file;
45   
46     /* Timeout waiting for process to quit */
47     guint quit_timeout;
48
49     /* Process ID */
50     GPid pid;
51 };
52
53 G_DEFINE_TYPE (Process, process, G_TYPE_OBJECT);
54
55 static Process *current_process = NULL;
56 static GHashTable *processes = NULL;
57 static int signal_pipe[2];
58
59 Process *
60 process_get_current (void)
61 {
62     if (current_process)
63         return current_process;
64
65     current_process = process_new ();
66     current_process->priv->pid = getpid ();
67
68     return current_process;
69 }
70
71 Process *
72 process_new (void)
73 {
74     return g_object_new (PROCESS_TYPE, NULL);
75 }
76
77 void
78 process_set_log_file (Process *process, const gchar *log_file)
79 {
80     g_return_if_fail (process != NULL);
81
82     g_free (process->priv->log_file);
83     process->priv->log_file = g_strdup (log_file);
84 }
85
86 const gchar *
87 process_get_log_file (Process *process)
88 {
89     g_return_val_if_fail (process != NULL, NULL);
90     return process->priv->log_file;
91 }
92   
93 void
94 process_set_env (Process *process, const gchar *name, const gchar *value)
95 {
96     g_return_if_fail (process != NULL);
97     g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));
98 }
99
100 const gchar *
101 process_get_env (Process *process, const gchar *name)
102 {
103     g_return_val_if_fail (process != NULL, FALSE);
104     return g_hash_table_lookup (process->priv->env, name);
105 }
106
107 static void
108 process_watch_cb (GPid pid, gint status, gpointer data)
109 {
110     Process *process = data;
111
112     if (WIFEXITED (status))
113     {
114         g_debug ("Process %d exited with return value %d", pid, WEXITSTATUS (status));
115         g_signal_emit (process, signals[EXITED], 0, WEXITSTATUS (status));
116     }
117     else if (WIFSIGNALED (status))
118     {
119         g_debug ("Process %d terminated with signal %d", pid, WTERMSIG (status));
120         g_signal_emit (process, signals[TERMINATED], 0, WTERMSIG (status));
121     }
122
123     if (process->priv->quit_timeout)
124         g_source_remove (process->priv->quit_timeout);
125     process->priv->quit_timeout = 0;  
126     process->priv->pid = 0;
127     g_hash_table_remove (processes, GINT_TO_POINTER (pid));
128
129     g_signal_emit (process, signals[STOPPED], 0);
130 }
131
132 static void
133 run_process (Process *process, char *const argv[])
134 {
135     GHashTableIter iter;
136     gpointer key, value;
137     int fd;
138
139     /* FIXME: Close existing file descriptors */
140
141     /* Make input non-blocking */
142     fd = g_open ("/dev/null", O_RDONLY);
143     dup2 (fd, STDIN_FILENO);
144     close (fd);
145
146     /* Set environment */
147     clearenv ();
148     g_hash_table_iter_init (&iter, process->priv->env);
149     while (g_hash_table_iter_next (&iter, &key, &value))
150         g_setenv ((gchar *)key, (gchar *)value, TRUE);
151
152     /* Set SIGUSR1 to ignore so the child process can indicate it when it is ready */
153     // FIXME: Should be in the xserver only
154     signal (SIGUSR1, SIG_IGN);
155
156     /* Make this process its own session so */
157     if (setsid () < 0)
158         g_warning ("Failed to make process a new session: %s", strerror (errno));
159
160     if (process->priv->user)
161     {
162         if (getuid () == 0)
163         {
164             if (initgroups (user_get_name (process->priv->user), user_get_gid (process->priv->user)) < 0)
165             {
166                 g_warning ("Failed to initialize supplementary groups for %s: %s", user_get_name (process->priv->user), strerror (errno));
167                 _exit (EXIT_FAILURE);
168             }
169
170             if (setgid (user_get_gid (process->priv->user)) != 0)
171             {
172                 g_warning ("Failed to set group ID to %d: %s", user_get_gid (process->priv->user), strerror (errno));
173                 _exit (EXIT_FAILURE);
174             }
175
176             if (setuid (user_get_uid (process->priv->user)) != 0)
177             {
178                 g_warning ("Failed to set user ID to %d: %s", user_get_uid (process->priv->user), strerror (errno));
179                 _exit (EXIT_FAILURE);
180             }
181         }
182
183         if (chdir (user_get_home_directory (process->priv->user)) != 0)
184         {
185             g_warning ("Failed to change to home directory %s: %s", user_get_home_directory (process->priv->user), strerror (errno));
186             _exit (EXIT_FAILURE);
187         }
188     }
189   
190     /* Redirect output to logfile */
191     if (process->priv->log_file)
192     {
193          int fd;
194
195          fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
196          if (fd < 0)
197              g_warning ("Failed to open log file %s: %s", process->priv->log_file, g_strerror (errno));
198          else
199          {
200              dup2 (fd, STDOUT_FILENO);
201              dup2 (fd, STDERR_FILENO);
202              close (fd);
203          }
204     }
205
206     execv (argv[0], argv);
207
208     g_warning ("Error executing child process %s: %s", argv[0], g_strerror (errno));
209     _exit (EXIT_FAILURE);
210 }
211
212 gboolean
213 process_start (Process *process,
214                User *user,
215                const gchar *working_dir,
216                const gchar *command,
217                GError **error)
218 {
219     gboolean result;
220     gint argc;
221     gchar **argv;
222     GString *string;
223     gpointer key, value;
224     GHashTableIter iter;
225     pid_t pid;
226
227     g_return_val_if_fail (process != NULL, FALSE);
228     g_return_val_if_fail (process->priv->pid == 0, FALSE);
229
230     process->priv->user = g_object_ref (user);
231
232     /* Create the log file owned by the target user */
233     if (process->priv->log_file)
234     {
235         gint fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
236         close (fd);
237         if (getuid () == 0 && chown (process->priv->log_file, user_get_uid (process->priv->user), user_get_gid (process->priv->user)) != 0)
238             g_warning ("Failed to set process log file ownership: %s", strerror (errno));
239     }
240
241     result = g_shell_parse_argv (command, &argc, &argv, error);
242     if (!result)
243         return FALSE;
244
245     pid = fork ();
246     if (pid < 0)
247     {
248         g_warning ("Failed to fork: %s", strerror (errno));
249         return FALSE;
250     }
251
252     if (pid == 0)
253         run_process (process, argv);
254     g_strfreev (argv);
255
256     string = g_string_new ("");
257     g_hash_table_iter_init (&iter, process->priv->env);
258     while (g_hash_table_iter_next (&iter, &key, &value))
259         g_string_append_printf (string, "%s=%s ", (gchar *)key, (gchar *)value);
260     g_string_append (string, command);
261     g_debug ("Launching process %d: %s", pid, string->str);
262     g_string_free (string, TRUE);
263
264     process->priv->pid = pid;
265
266     g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
267     g_child_watch_add (process->priv->pid, process_watch_cb, process);
268
269     g_signal_emit (process, signals[STARTED], 0);  
270
271     return TRUE;
272 }
273
274 gboolean
275 process_get_is_running (Process *process)
276 {
277     g_return_val_if_fail (process != NULL, FALSE);
278     return process->priv->pid != 0;
279 }
280
281 GPid
282 process_get_pid (Process *process)
283 {
284     g_return_val_if_fail (process != NULL, 0);
285     return process->priv->pid;
286 }
287
288 void
289 process_signal (Process *process, int signum)
290 {
291     g_return_if_fail (process != NULL);
292
293     if (process->priv->pid == 0)
294         return;
295
296     g_debug ("Sending signal %d to process %d", signum, process->priv->pid);
297
298     if (kill (process->priv->pid, signum) < 0)
299         g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
300 }
301
302 static gboolean
303 quit_timeout_cb (Process *process)
304 {
305     process->priv->quit_timeout = 0;
306     process_signal (process, SIGKILL);
307     return FALSE;
308 }
309
310 void
311 process_stop (Process *process)
312 {
313     /* Send SIGTERM, and then SIGKILL if no response */
314     process->priv->quit_timeout = g_timeout_add (5000, (GSourceFunc) quit_timeout_cb, process);
315     process_signal (process, SIGTERM);
316 }
317
318 static void
319 process_init (Process *process)
320 {
321     process->priv = G_TYPE_INSTANCE_GET_PRIVATE (process, PROCESS_TYPE, ProcessPrivate);
322     process->priv->env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
323 }
324
325 static void
326 process_finalize (GObject *object)
327 {
328     Process *self;
329
330     self = PROCESS (object);
331
332     if (self->priv->user)
333         g_object_unref (self->priv->user);
334
335     if (self->priv->pid > 0)
336         g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
337
338     if (self->priv->pid)
339         kill (self->priv->pid, SIGTERM);
340
341     g_hash_table_unref (self->priv->env);
342
343     G_OBJECT_CLASS (process_parent_class)->finalize (object);
344 }
345
346 static void
347 signal_cb (int signum, siginfo_t *info, void *data)
348 {
349     /* NOTE: Using g_printerr as can't call g_warning from a signal callback */
350     if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
351         write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
352         g_printerr ("Failed to write to signal pipe: %s", strerror (errno));
353 }
354
355 static gboolean
356 handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
357 {
358     int signo;
359     pid_t pid;
360     Process *process;
361
362     if (read (signal_pipe[0], &signo, sizeof (int)) < 0 || 
363         read (signal_pipe[0], &pid, sizeof (pid_t)) < 0)
364     {
365         g_warning ("Error reading from signal pipe: %s", strerror (errno));
366         return TRUE;
367     }
368
369     g_debug ("Got signal %d from process %d", signo, pid);
370
371     process = g_hash_table_lookup (processes, GINT_TO_POINTER (pid));
372     if (process == NULL)
373         process = process_get_current ();
374     if (process)
375         g_signal_emit (process, signals[GOT_SIGNAL], 0, signo);
376
377     return TRUE;
378 }
379
380 static void
381 process_class_init (ProcessClass *klass)
382 {
383     GObjectClass *object_class = G_OBJECT_CLASS (klass);
384     struct sigaction action;
385
386     object_class->finalize = process_finalize;  
387
388     g_type_class_add_private (klass, sizeof (ProcessPrivate));
389
390     signals[STARTED] =
391         g_signal_new ("started",
392                       G_TYPE_FROM_CLASS (klass),
393                       G_SIGNAL_RUN_LAST,
394                       G_STRUCT_OFFSET (ProcessClass, started),
395                       NULL, NULL,
396                       g_cclosure_marshal_VOID__VOID,
397                       G_TYPE_NONE, 0); 
398     signals[GOT_DATA] =
399         g_signal_new ("got-data",
400                       G_TYPE_FROM_CLASS (klass),
401                       G_SIGNAL_RUN_LAST,
402                       G_STRUCT_OFFSET (ProcessClass, got_data),
403                       NULL, NULL,
404                       g_cclosure_marshal_VOID__VOID,
405                       G_TYPE_NONE, 0); 
406     signals[GOT_SIGNAL] =
407         g_signal_new ("got-signal",
408                       G_TYPE_FROM_CLASS (klass),
409                       G_SIGNAL_RUN_LAST,
410                       G_STRUCT_OFFSET (ProcessClass, got_signal),
411                       NULL, NULL,
412                       g_cclosure_marshal_VOID__INT,
413                       G_TYPE_NONE, 1, G_TYPE_INT);
414     signals[EXITED] =
415         g_signal_new ("exited",
416                       G_TYPE_FROM_CLASS (klass),
417                       G_SIGNAL_RUN_LAST,
418                       G_STRUCT_OFFSET (ProcessClass, exited),
419                       NULL, NULL,
420                       g_cclosure_marshal_VOID__INT,
421                       G_TYPE_NONE, 1, G_TYPE_INT);
422     signals[TERMINATED] =
423         g_signal_new ("terminated",
424                       G_TYPE_FROM_CLASS (klass),
425                       G_SIGNAL_RUN_LAST,
426                       G_STRUCT_OFFSET (ProcessClass, terminated),
427                       NULL, NULL,
428                       g_cclosure_marshal_VOID__INT,
429                       G_TYPE_NONE, 1, G_TYPE_INT);
430     signals[STOPPED] =
431         g_signal_new ("stopped",
432                       G_TYPE_FROM_CLASS (klass),
433                       G_SIGNAL_RUN_LAST,
434                       G_STRUCT_OFFSET (ProcessClass, stopped),
435                       NULL, NULL,
436                       g_cclosure_marshal_VOID__VOID,
437                       G_TYPE_NONE, 0);
438
439     /* Catch signals and feed them to the main loop via a pipe */
440     processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
441     if (pipe (signal_pipe) != 0)
442         g_critical ("Failed to create signal pipe");
443     g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
444     action.sa_sigaction = signal_cb;
445     sigemptyset (&action.sa_mask);
446     action.sa_flags = SA_SIGINFO;
447     sigaction (SIGTERM, &action, NULL);
448     sigaction (SIGINT, &action, NULL);
449     sigaction (SIGHUP, &action, NULL);
450     sigaction (SIGUSR1, &action, NULL);
451     sigaction (SIGUSR2, &action, NULL);
452 }