]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/unity-system-compositor.c
Fix autologin use case and add a test for it
[sojka/lightdm.git] / src / unity-system-compositor.c
1 /*
2  * Copyright (C) 2013 Canonical Ltd.
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 <config.h>
13 #include <string.h>
14 #include <fcntl.h>
15 #include <unistd.h>
16 #include <sys/stat.h>
17 #include <errno.h>
18 #include <glib/gstdio.h>
19 #include <stdlib.h>
20
21 #include "unity-system-compositor.h"
22 #include "configuration.h"
23 #include "process.h"
24 #include "greeter.h"
25 #include "vt.h"
26
27 struct UnitySystemCompositorPrivate
28 {
29     /* Compositor process */
30     Process *process;
31   
32     /* File to log to */
33     gchar *log_file;    
34
35     /* Command to run the compositor */
36     gchar *command;
37
38     /* Socket to communicate on */
39     gchar *socket;
40
41     /* VT to run on */
42     gint vt;
43     gboolean have_vt_ref;
44
45     /* Pipes to communicate with compositor */
46     int to_compositor_pipe[2];
47     int from_compositor_pipe[2];
48
49     /* IO channel listening on for messages from the compositor */
50     GIOChannel *from_compositor_channel;
51     guint from_compositor_watch;
52
53     /* Buffer reading from channel */
54     guint8 *read_buffer;
55     gsize read_buffer_length;
56     gsize read_buffer_n_used;
57
58     /* Timeout when waiting for compositor to start */
59     gint timeout;
60     guint timeout_source;
61
62     /* TRUE when received ready signal */
63     gboolean is_ready;
64 };
65
66 G_DEFINE_TYPE (UnitySystemCompositor, unity_system_compositor, DISPLAY_SERVER_TYPE);
67
68 typedef enum
69 {
70    USC_MESSAGE_PING = 0,
71    USC_MESSAGE_PONG = 1,
72    USC_MESSAGE_READY = 2,
73    USC_MESSAGE_SESSION_CONNECTED = 3,
74    USC_MESSAGE_SET_ACTIVE_SESSION = 4,
75    USC_MESSAGE_SET_NEXT_SESSION = 5,
76 } USCMessageID;
77
78 UnitySystemCompositor *
79 unity_system_compositor_new (void)
80 {
81     return g_object_new (UNITY_SYSTEM_COMPOSITOR_TYPE, NULL);
82 }
83
84 void
85 unity_system_compositor_set_command (UnitySystemCompositor *compositor, const gchar *command)
86 {
87     g_return_if_fail (compositor != NULL);
88     g_free (compositor->priv->command);
89     compositor->priv->command = g_strdup (command);
90 }
91
92 void
93 unity_system_compositor_set_socket (UnitySystemCompositor *compositor, const gchar *socket)
94 {
95     g_return_if_fail (compositor != NULL);
96     g_free (compositor->priv->socket);
97     compositor->priv->socket = g_strdup (socket);
98 }
99
100 const gchar *
101 unity_system_compositor_get_socket (UnitySystemCompositor *compositor)
102 {
103     g_return_val_if_fail (compositor != NULL, NULL);
104     return compositor->priv->socket;
105 }
106
107 void
108 unity_system_compositor_set_vt (UnitySystemCompositor *compositor, gint vt)
109 {
110     g_return_if_fail (compositor != NULL);
111
112     if (compositor->priv->have_vt_ref)
113         vt_unref (compositor->priv->vt);
114     compositor->priv->have_vt_ref = FALSE;
115     compositor->priv->vt = vt;
116     if (vt > 0)
117     {
118         vt_ref (vt);
119         compositor->priv->have_vt_ref = TRUE;
120     }
121 }
122
123 void
124 unity_system_compositor_set_timeout (UnitySystemCompositor *compositor, gint timeout)
125 {
126     g_return_if_fail (compositor != NULL);
127     compositor->priv->timeout = timeout;
128 }
129
130 static void
131 write_message (UnitySystemCompositor *compositor, guint16 id, const guint8 *payload, guint16 payload_length)
132 {
133     guint8 *data;
134     gsize data_length = 4 + payload_length;
135
136     data = g_malloc (data_length);
137     data[0] = id >> 8;
138     data[1] = id & 0xFF;
139     data[2] = payload_length >> 8;
140     data[3] = payload_length & 0xFF;
141     memcpy (data + 4, payload, payload_length);
142
143     errno = 0;
144     if (write (compositor->priv->to_compositor_pipe[1], data, data_length) != data_length)
145         l_warning (compositor, "Failed to write to compositor: %s", strerror (errno));
146 }
147
148 void
149 unity_system_compositor_set_active_session (UnitySystemCompositor *compositor, const gchar *id)
150 {
151     g_return_if_fail (compositor != NULL);
152     write_message (compositor, USC_MESSAGE_SET_ACTIVE_SESSION, (const guint8 *) id, strlen (id));
153 }
154
155 void
156 unity_system_compositor_set_next_session (UnitySystemCompositor *compositor, const gchar *id)
157 {
158     g_return_if_fail (compositor != NULL);
159     write_message (compositor, USC_MESSAGE_SET_NEXT_SESSION, (const guint8 *) id, strlen (id));
160 }
161
162 static gint
163 unity_system_compositor_get_vt (DisplayServer *server)
164 {
165     g_return_val_if_fail (server != NULL, 0);
166     return UNITY_SYSTEM_COMPOSITOR (server)->priv->vt;
167 }
168
169 static void
170 unity_system_compositor_connect_session (DisplayServer *display_server, Session *session)
171 {
172     UnitySystemCompositor *compositor = UNITY_SYSTEM_COMPOSITOR (display_server);
173     const gchar *name;
174
175     if (compositor->priv->socket)
176         session_set_env (session, "MIR_SOCKET", compositor->priv->socket);
177     if (IS_GREETER (session))
178         name = "greeter-0";
179     else
180         name = "session-0";
181     session_set_env (session, "MIR_SERVER_NAME", name);
182
183     if (compositor->priv->vt >= 0)
184     {
185         gchar *value = g_strdup_printf ("%d", compositor->priv->vt);
186         session_set_env (session, "XDG_VTNR", value);
187         g_free (value);
188     }
189 }
190
191 static void
192 unity_system_compositor_disconnect_session (DisplayServer *display_server, Session *session)
193 {
194     session_unset_env (session, "MIR_SOCKET");
195     session_unset_env (session, "MIR_SERVER_NAME");
196     session_unset_env (session, "XDG_VTNR");
197 }
198
199 static gchar *
200 get_absolute_command (const gchar *command)
201 {
202     gchar **tokens;
203     gchar *absolute_binary, *absolute_command = NULL;
204
205     tokens = g_strsplit (command, " ", 2);
206
207     absolute_binary = g_find_program_in_path (tokens[0]);
208     if (absolute_binary)
209     {
210         if (tokens[1])
211             absolute_command = g_strjoin (" ", absolute_binary, tokens[1], NULL);
212         else
213             absolute_command = g_strdup (absolute_binary);
214     }
215     g_free (absolute_binary);
216
217     g_strfreev (tokens);
218
219     return absolute_command;
220 }
221
222 static gboolean
223 read_cb (GIOChannel *source, GIOCondition condition, gpointer data)
224 {
225     UnitySystemCompositor *compositor = data;
226     gsize n_to_read = 0;
227     guint16 id, payload_length;
228     /*guint8 *payload;*/
229   
230     if (condition == G_IO_HUP)
231     {
232         l_debug (compositor, "Compositor closed communication channel");
233         return FALSE;
234     }
235
236     /* Work out how much required for a message */
237     if (compositor->priv->read_buffer_n_used < 4)
238         n_to_read = 4 - compositor->priv->read_buffer_n_used;
239     else
240     {
241         payload_length = compositor->priv->read_buffer[2] << 8 | compositor->priv->read_buffer[3];
242         n_to_read = 4 + payload_length - compositor->priv->read_buffer_n_used;
243     }
244
245     /* Read from compositor */
246     if (n_to_read > 0)
247     {
248         gsize n_total, n_read = 0;
249         GIOStatus status;
250         GError *error = NULL;
251
252         n_total = compositor->priv->read_buffer_n_used + n_to_read;
253         if (compositor->priv->read_buffer_length < n_total)
254             compositor->priv->read_buffer = g_realloc (compositor->priv->read_buffer, n_total);
255
256         status = g_io_channel_read_chars (source,
257                                           (gchar *)compositor->priv->read_buffer + compositor->priv->read_buffer_n_used,
258                                           n_to_read,
259                                           &n_read,
260                                           &error);
261         if (error)
262             l_warning (compositor, "Failed to read from compositor: %s", error->message);
263         if (status != G_IO_STATUS_NORMAL)
264             return TRUE;
265         g_clear_error (&error);
266         compositor->priv->read_buffer_n_used += n_read;
267     }
268
269     /* Read header */
270     if (compositor->priv->read_buffer_n_used < 4)
271          return TRUE;
272     id = compositor->priv->read_buffer[0] << 8 | compositor->priv->read_buffer[1];
273     payload_length = compositor->priv->read_buffer[2] << 8 | compositor->priv->read_buffer[3];
274
275     /* Read payload */
276     if (compositor->priv->read_buffer_n_used < 4 + payload_length)
277         return TRUE;
278     /*payload = compositor->priv->read_buffer + 4;*/
279
280     switch (id)
281     {
282     case USC_MESSAGE_PING:
283         l_debug (compositor, "PING!");
284         write_message (compositor, USC_MESSAGE_PONG, NULL, 0);
285         break;
286     case USC_MESSAGE_PONG:
287         l_debug (compositor, "PONG!");
288         break;
289     case USC_MESSAGE_READY:
290         l_debug (compositor, "READY");
291         if (!compositor->priv->is_ready)
292         {
293             compositor->priv->is_ready = TRUE;
294             l_debug (compositor, "Compositor ready");
295             g_source_remove (compositor->priv->timeout_source);
296             compositor->priv->timeout_source = 0;
297             DISPLAY_SERVER_CLASS (unity_system_compositor_parent_class)->start (DISPLAY_SERVER (compositor));
298         }
299         break;
300     case USC_MESSAGE_SESSION_CONNECTED:
301         l_debug (compositor, "SESSION CONNECTED");
302         break;
303     default:
304         l_warning (compositor, "Ignoring unknown message %d with %d octets from system compositor", id, payload_length);
305         break;
306     }
307
308     /* Clear buffer */
309     compositor->priv->read_buffer_n_used = 0;
310
311     return TRUE;
312 }
313
314 static void
315 run_cb (Process *process, UnitySystemCompositor *compositor)
316 {
317     int fd;
318
319     /* Make input non-blocking */
320     fd = open ("/dev/null", O_RDONLY);
321     dup2 (fd, STDIN_FILENO);
322     close (fd);
323
324     /* Redirect output to logfile */
325     if (compositor->priv->log_file)
326     {
327          int fd;
328          gchar *old_filename;
329
330          /* Move old file out of the way */
331          old_filename = g_strdup_printf ("%s.old", compositor->priv->log_file);
332          rename (compositor->priv->log_file, old_filename);
333          g_free (old_filename);
334
335          /* Create new file and log to it */
336          fd = g_open (compositor->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
337          if (fd < 0)
338              l_warning (compositor, "Failed to open log file %s: %s", compositor->priv->log_file, g_strerror (errno));
339          else
340          {
341              dup2 (fd, STDOUT_FILENO);
342              dup2 (fd, STDERR_FILENO);
343              close (fd);
344          }
345     }
346 }
347
348 static gboolean
349 timeout_cb (gpointer data)
350 {
351     UnitySystemCompositor *compositor = data;
352
353     /* Stop the compositor - it is not working */
354     display_server_stop (DISPLAY_SERVER (compositor));
355
356     compositor->priv->timeout_source = 0;
357
358     return TRUE;
359 }
360
361 static void
362 stopped_cb (Process *process, UnitySystemCompositor *compositor)
363 {
364     l_debug (compositor, "Unity system compositor stopped");
365
366     if (compositor->priv->timeout_source != 0)
367         g_source_remove (compositor->priv->timeout_source);
368     compositor->priv->timeout_source = 0;
369
370     /* Release VT and display number for re-use */
371     if (compositor->priv->have_vt_ref)
372     {
373         vt_unref (compositor->priv->vt);
374         compositor->priv->have_vt_ref = FALSE;
375     }
376
377     DISPLAY_SERVER_CLASS (unity_system_compositor_parent_class)->stop (DISPLAY_SERVER (compositor));
378 }
379
380 static gboolean
381 unity_system_compositor_start (DisplayServer *server)
382 {
383     UnitySystemCompositor *compositor = UNITY_SYSTEM_COMPOSITOR (server);
384     gboolean result;
385     gchar *dir, *command, *absolute_command, *value;
386
387     g_return_val_if_fail (compositor->priv->process == NULL, FALSE);
388
389     compositor->priv->is_ready = FALSE;
390
391     g_return_val_if_fail (compositor->priv->command != NULL, FALSE);
392
393     /* Create pipes to talk to compositor */
394     if (pipe (compositor->priv->to_compositor_pipe) < 0 || pipe (compositor->priv->from_compositor_pipe) < 0)
395     {
396         l_debug (compositor, "Failed to create compositor pipes: %s", g_strerror (errno));
397         return FALSE;
398     }
399
400     /* Don't allow the daemon end of the pipes to be accessed in the compositor */
401     fcntl (compositor->priv->to_compositor_pipe[1], F_SETFD, FD_CLOEXEC);
402     fcntl (compositor->priv->from_compositor_pipe[0], F_SETFD, FD_CLOEXEC);
403
404     /* Listen for messages from the compositor */
405     compositor->priv->from_compositor_channel = g_io_channel_unix_new (compositor->priv->from_compositor_pipe[0]);
406     compositor->priv->from_compositor_watch = g_io_add_watch (compositor->priv->from_compositor_channel, G_IO_IN | G_IO_HUP, read_cb, compositor);
407
408     /* Setup logging */
409     dir = config_get_string (config_get_instance (), "LightDM", "log-directory");
410     compositor->priv->log_file = g_build_filename (dir, "unity-system-compositor.log", NULL);
411     l_debug (compositor, "Logging to %s", compositor->priv->log_file);
412     g_free (dir);
413
414     /* Setup environment */
415     compositor->priv->process = process_new ();
416     process_set_clear_environment (compositor->priv->process, TRUE);
417     process_set_env (compositor->priv->process, "XDG_SEAT", "seat0");
418     value = g_strdup_printf ("%d", compositor->priv->vt);
419     process_set_env (compositor->priv->process, "XDG_VTNR", value);
420     g_free (value);
421     /* Variable required for regression tests */
422     if (g_getenv ("LIGHTDM_TEST_ROOT"))
423     {
424         process_set_env (compositor->priv->process, "LIGHTDM_TEST_ROOT", g_getenv ("LIGHTDM_TEST_ROOT"));
425         process_set_env (compositor->priv->process, "LD_PRELOAD", g_getenv ("LD_PRELOAD"));
426         process_set_env (compositor->priv->process, "LD_LIBRARY_PATH", g_getenv ("LD_LIBRARY_PATH"));
427     }
428
429     command = g_strdup_printf ("%s --file '%s' --from-dm-fd %d --to-dm-fd %d --vt %d", compositor->priv->command, compositor->priv->socket, compositor->priv->to_compositor_pipe[0], compositor->priv->from_compositor_pipe[1], compositor->priv->vt);
430     absolute_command = get_absolute_command (command);
431     g_free (command);
432
433     /* Start the compositor */
434     process_set_command (compositor->priv->process, absolute_command);
435     g_free (absolute_command);
436     g_signal_connect (compositor->priv->process, "stopped", G_CALLBACK (stopped_cb), compositor);
437     g_signal_connect (compositor->priv->process, "run", G_CALLBACK (run_cb), compositor);
438     result = process_start (compositor->priv->process, FALSE);
439
440     /* Close compostor ends of the pipes */
441     close (compositor->priv->to_compositor_pipe[0]);
442     compositor->priv->to_compositor_pipe[0] = 0;
443     close (compositor->priv->from_compositor_pipe[1]);
444     compositor->priv->from_compositor_pipe[1] = 0;
445
446     if (!result)
447         return FALSE;
448
449     /* Connect to the compositor */
450     if (compositor->priv->timeout > 0)
451     {
452         l_debug (compositor, "Waiting for system compositor for %ds", compositor->priv->timeout);
453         compositor->priv->timeout_source = g_timeout_add (compositor->priv->timeout * 1000, timeout_cb, compositor);
454     }
455
456     return TRUE;
457 }
458  
459 static void
460 unity_system_compositor_stop (DisplayServer *server)
461 {
462     process_stop (UNITY_SYSTEM_COMPOSITOR (server)->priv->process);
463 }
464
465 static void
466 unity_system_compositor_init (UnitySystemCompositor *compositor)
467 {
468     compositor->priv = G_TYPE_INSTANCE_GET_PRIVATE (compositor, UNITY_SYSTEM_COMPOSITOR_TYPE, UnitySystemCompositorPrivate);
469     compositor->priv->vt = -1;
470     compositor->priv->command = g_strdup ("unity-system-compositor");
471     compositor->priv->socket = g_strdup ("/tmp/mir_socket");
472     compositor->priv->timeout = -1;
473 }
474
475 static void
476 unity_system_compositor_finalize (GObject *object)
477 {
478     UnitySystemCompositor *self;
479
480     self = UNITY_SYSTEM_COMPOSITOR (object);  
481
482     if (self->priv->process)
483     {
484         g_signal_handlers_disconnect_matched (self->priv->process, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
485         g_object_unref (self->priv->process);
486     }
487     g_free (self->priv->log_file);
488     g_free (self->priv->command);
489     g_free (self->priv->socket);
490     if (self->priv->have_vt_ref)
491         vt_unref (self->priv->vt);
492     close (self->priv->to_compositor_pipe[0]);
493     close (self->priv->to_compositor_pipe[1]);
494     close (self->priv->from_compositor_pipe[0]);
495     close (self->priv->from_compositor_pipe[1]);
496     g_io_channel_unref (self->priv->from_compositor_channel);
497     g_source_remove (self->priv->from_compositor_watch);
498     g_free (self->priv->read_buffer);
499     if (self->priv->timeout_source != 0)
500         g_source_remove (self->priv->timeout_source);
501
502     G_OBJECT_CLASS (unity_system_compositor_parent_class)->finalize (object);
503 }
504
505 static void
506 unity_system_compositor_class_init (UnitySystemCompositorClass *klass)
507 {
508     GObjectClass *object_class = G_OBJECT_CLASS (klass);
509     DisplayServerClass *display_server_class = DISPLAY_SERVER_CLASS (klass);
510
511     display_server_class->get_vt = unity_system_compositor_get_vt;
512     display_server_class->connect_session = unity_system_compositor_connect_session;
513     display_server_class->disconnect_session = unity_system_compositor_disconnect_session;
514     display_server_class->start = unity_system_compositor_start;
515     display_server_class->stop = unity_system_compositor_stop;
516     object_class->finalize = unity_system_compositor_finalize;
517
518     g_type_class_add_private (klass, sizeof (UnitySystemCompositorPrivate));
519 }