1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2015 Red Hat, Inc.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 * 02110-1301, USA.
19 */
20 #include "config.h"
21
22 #include <locale.h>
23 #include <sysexits.h>
24
25 #include "gdm-common.h"
26 #include "gdm-settings-direct.h"
27 #include "gdm-settings-keys.h"
28 #include "gdm-log.h"
29
30 #include "gdm-manager-glue.h"
31
32 #include <glib/gi18n.h>
33 #include <glib/gstdio.h>
34 #include <glib-unix.h>
35 #include <glib.h>
36
37 #include <gio/gunixinputstream.h>
38
39 #define BUS_ADDRESS_FILENO (STDERR_FILENO + 1)
40
41 typedef struct
42 {
43 GdmSettings *settings;
44 GCancellable *cancellable;
45
46 GSubprocess *bus_subprocess;
47 GDBusConnection *bus_connection;
48 char *bus_address;
49
50 char **environment;
51
52 GSubprocess *session_subprocess;
53 char *session_command;
54 int session_exit_status;
55
56 GMainLoop *main_loop;
57
58 guint32 debug_enabled : 1;
59 } State;
60
61 static void
on_bus_finished(GSubprocess * subprocess,GAsyncResult * result,State * state)62 on_bus_finished (GSubprocess *subprocess,
63 GAsyncResult *result,
64 State *state)
65 {
66 gboolean cancelled;
67
68 cancelled = !g_subprocess_wait_finish (subprocess, result, NULL);
69
70 if (cancelled) {
71 goto out;
72 }
73
74 if (g_subprocess_get_if_exited (subprocess)) {
75 int exit_status;
76
77 exit_status = g_subprocess_get_exit_status (subprocess);
78
79 g_debug ("message bus exited with status %d", exit_status);
80 } else {
81 int signal_number;
82
83 signal_number = g_subprocess_get_term_sig (subprocess);
84 g_debug ("message bus was killed with status %d", signal_number);
85 }
86
87 g_clear_object (&state->bus_subprocess);
88 out:
89 g_main_loop_quit (state->main_loop);
90 }
91
92 static gboolean
spawn_bus(State * state,GCancellable * cancellable)93 spawn_bus (State *state,
94 GCancellable *cancellable)
95 {
96 GDBusConnection *bus_connection = NULL;
97 GPtrArray *arguments = NULL;
98 GSubprocessLauncher *launcher = NULL;
99 GSubprocess *subprocess = NULL;
100 GInputStream *input_stream = NULL;
101 GDataInputStream *data_stream = NULL;
102 GError *error = NULL;
103 char *bus_address_fd_string = NULL;
104 char *bus_address = NULL;
105 gsize bus_address_size;
106
107 gboolean is_running = FALSE;
108 int ret;
109 int pipe_fds[2];
110
111 g_debug ("Running session message bus");
112
113 bus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
114 cancellable,
115 NULL);
116
117 if (bus_connection != NULL) {
118 g_debug ("session message bus already running, not starting another one");
119 state->bus_connection = bus_connection;
120 return TRUE;
121 }
122
123 ret = g_unix_open_pipe (pipe_fds, FD_CLOEXEC, &error);
124
125 if (!ret) {
126 g_debug ("could not open pipe: %s", error->message);
127 goto out;
128 }
129
130 arguments = g_ptr_array_new ();
131 launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
132 g_subprocess_launcher_take_fd (launcher, pipe_fds[1], BUS_ADDRESS_FILENO);
133
134 bus_address_fd_string = g_strdup_printf ("%d", BUS_ADDRESS_FILENO);
135
136 g_ptr_array_add (arguments, "dbus-daemon");
137
138 g_ptr_array_add (arguments, "--print-address");
139 g_ptr_array_add (arguments, bus_address_fd_string);
140 g_ptr_array_add (arguments, "--session");
141 g_ptr_array_add (arguments, NULL);
142
143 subprocess = g_subprocess_launcher_spawnv (launcher,
144 (const char * const *) arguments->pdata,
145 &error);
146 g_free (bus_address_fd_string);
147 g_clear_object (&launcher);
148 g_ptr_array_free (arguments, TRUE);
149
150 if (subprocess == NULL) {
151 g_debug ("could not start dbus-daemon: %s", error->message);
152 goto out;
153 }
154
155 input_stream = g_unix_input_stream_new (pipe_fds[0], TRUE);
156 data_stream = g_data_input_stream_new (input_stream);
157 g_clear_object (&input_stream);
158
159 bus_address = g_data_input_stream_read_line (data_stream,
160 &bus_address_size,
161 cancellable,
162 &error);
163
164 if (error != NULL) {
165 g_debug ("could not read address from session message bus: %s", error->message);
166 goto out;
167 }
168
169 if (bus_address == NULL) {
170 g_debug ("session message bus did not write address");
171 goto out;
172 }
173
174 state->bus_address = bus_address;
175
176 state->bus_subprocess = g_object_ref (subprocess);
177
178 g_subprocess_wait_async (state->bus_subprocess,
179 cancellable,
180 (GAsyncReadyCallback)
181 on_bus_finished,
182 state);
183
184 bus_connection = g_dbus_connection_new_for_address_sync (state->bus_address,
185 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
186 G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
187 NULL,
188 cancellable,
189 &error);
190
191 if (bus_connection == NULL) {
192 g_debug ("could not open connection to session bus: %s",
193 error->message);
194 goto out;
195 }
196
197 state->bus_connection = bus_connection;
198 is_running = TRUE;
199 out:
200 g_clear_object (&data_stream);
201 g_clear_object (&subprocess);
202 g_clear_object (&launcher);
203 g_clear_error (&error);
204
205 return is_running;
206 }
207
208 static gboolean
import_environment(State * state,GCancellable * cancellable)209 import_environment (State *state,
210 GCancellable *cancellable)
211 {
212 g_autoptr(GVariant) reply = NULL;
213 g_autoptr(GVariant) environment_variant = NULL;
214 g_autoptr(GError) error = NULL;
215
216 reply = g_dbus_connection_call_sync (state->bus_connection,
217 "org.freedesktop.systemd1",
218 "/org/freedesktop/systemd1",
219 "org.freedesktop.DBus.Properties",
220 "Get",
221 g_variant_new ("(ss)",
222 "org.freedesktop.systemd1.Manager",
223 "Environment"),
224 NULL,
225 G_DBUS_CALL_FLAGS_NONE,
226 -1, cancellable, &error);
227
228 if (reply == NULL) {
229 g_debug ("could not fetch environment: %s", error->message);
230 return FALSE;
231 }
232
233 g_variant_get (reply, "(v)", &environment_variant);
234
235 state->environment = g_variant_dup_strv (environment_variant, NULL);
236
237 return TRUE;
238 }
239
240 static void
on_session_finished(GSubprocess * subprocess,GAsyncResult * result,State * state)241 on_session_finished (GSubprocess *subprocess,
242 GAsyncResult *result,
243 State *state)
244 {
245 gboolean cancelled;
246
247 cancelled = !g_subprocess_wait_finish (subprocess, result, NULL);
248
249 if (cancelled) {
250 goto out;
251 }
252
253 if (g_subprocess_get_if_exited (subprocess)) {
254 int exit_status;
255
256 exit_status = g_subprocess_get_exit_status (subprocess);
257
258 g_debug ("session exited with status %d", exit_status);
259
260 state->session_exit_status = exit_status;
261 } else {
262 int signal_number;
263
264 signal_number = g_subprocess_get_term_sig (subprocess);
265 g_debug ("session was killed with status %d", signal_number);
266 }
267
268 g_clear_object (&state->session_subprocess);
269 out:
270 g_main_loop_quit (state->main_loop);
271 }
272
273 static gboolean
spawn_session(State * state,GCancellable * cancellable)274 spawn_session (State *state,
275 GCancellable *cancellable)
276 {
277 GSubprocessLauncher *launcher = NULL;
278 GSubprocess *subprocess = NULL;
279 GError *error = NULL;
280 gboolean is_running = FALSE;
281 int ret;
282 char **argv = NULL;
283 static const char *session_variables[] = { "DISPLAY",
284 "XAUTHORITY",
285 "WAYLAND_DISPLAY",
286 "WAYLAND_SOCKET",
287 "GNOME_SHELL_SESSION_MODE",
288 NULL };
289
290 g_debug ("Running wayland session");
291
292 ret = g_shell_parse_argv (state->session_command,
293 NULL,
294 &argv,
295 &error);
296
297 if (!ret) {
298 g_debug ("could not parse session arguments: %s", error->message);
299 goto out;
300 }
301
302 launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
303
304 if (state->environment != NULL) {
305 size_t i;
306
307 for (i = 0; state->environment[i] != NULL; i++) {
308 g_auto(GStrv) environment_entry = NULL;
309
310 if (state->environment[i][0] == '\0') {
311 continue;
312 }
313
314 environment_entry = g_strsplit (state->environment[i], "=", 2);
315
316 if (environment_entry[0] == NULL || environment_entry[1] == NULL) {
317 continue;
318 }
319
320 g_subprocess_launcher_setenv (launcher, environment_entry[0], environment_entry[1], FALSE);
321 }
322
323 /* Don't allow session specific environment variables from earlier sessions to
324 * leak through */
325 for (i = 0; session_variables[i] != NULL; i++) {
326 if (g_getenv (session_variables[i]) == NULL) {
327 g_subprocess_launcher_unsetenv (launcher, session_variables[i]);
328 }
329 }
330 }
331
332 if (state->bus_address != NULL) {
333 g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", state->bus_address, TRUE);
334 }
335
336 subprocess = g_subprocess_launcher_spawnv (launcher,
337 (const char * const *) argv,
338 &error);
339 g_strfreev (argv);
340
341 if (subprocess == NULL) {
342 g_debug ("could not start session: %s", error->message);
343 goto out;
344 }
345
346 state->session_subprocess = g_object_ref (subprocess);
347
348 g_subprocess_wait_async (state->session_subprocess,
349 cancellable,
350 (GAsyncReadyCallback)
351 on_session_finished,
352 state);
353
354 is_running = TRUE;
355 out:
356 g_clear_object (&subprocess);
357 return is_running;
358 }
359
360 static void
signal_subprocesses(State * state)361 signal_subprocesses (State *state)
362 {
363 if (state->session_subprocess != NULL) {
364 g_subprocess_send_signal (state->session_subprocess, SIGTERM);
365 }
366
367 if (state->bus_subprocess != NULL) {
368 g_subprocess_send_signal (state->bus_subprocess, SIGTERM);
369 }
370 }
371
372 static void
wait_on_subprocesses(State * state)373 wait_on_subprocesses (State *state)
374 {
375 if (state->bus_subprocess != NULL) {
376 g_subprocess_wait (state->bus_subprocess, NULL, NULL);
377 }
378
379 if (state->session_subprocess != NULL) {
380 g_subprocess_wait (state->session_subprocess, NULL, NULL);
381 }
382 }
383
384 static gboolean
register_display(State * state,GCancellable * cancellable)385 register_display (State *state,
386 GCancellable *cancellable)
387 {
388 GdmDBusManager *manager = NULL;
389 GError *error = NULL;
390 gboolean registered = FALSE;
391 GVariantBuilder details;
392
393 manager = gdm_dbus_manager_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
394 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
395 G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
396 "org.gnome.DisplayManager",
397 "/org/gnome/DisplayManager/Manager",
398 cancellable,
399 &error);
400
401 if (!manager) {
402 g_debug ("could not contact display manager: %s", error->message);
403 g_error_free (error);
404 goto out;
405 }
406
407 g_variant_builder_init (&details, G_VARIANT_TYPE ("a{ss}"));
408 g_variant_builder_add (&details, "{ss}", "session-type", "wayland");
409
410 registered = gdm_dbus_manager_call_register_display_sync (manager,
411 g_variant_builder_end (&details),
412 cancellable,
413 &error);
414 if (error != NULL) {
415 g_debug ("Could not register display: %s", error->message);
416 g_error_free (error);
417 }
418
419 out:
420 g_clear_object (&manager);
421 return registered;
422 }
423
424 static void
init_state(State ** state)425 init_state (State **state)
426 {
427 static State state_allocation;
428
429 *state = &state_allocation;
430 }
431
432 static void
clear_state(State ** out_state)433 clear_state (State **out_state)
434 {
435 State *state = *out_state;
436
437 g_clear_object (&state->cancellable);
438 g_clear_object (&state->bus_connection);
439 g_clear_object (&state->session_subprocess);
440 g_clear_pointer (&state->environment, g_strfreev);
441 g_clear_pointer (&state->main_loop, g_main_loop_unref);
442 *out_state = NULL;
443 }
444
445 static gboolean
on_sigterm(State * state)446 on_sigterm (State *state)
447 {
448 g_cancellable_cancel (state->cancellable);
449
450 if (g_main_loop_is_running (state->main_loop)) {
451 g_main_loop_quit (state->main_loop);
452 }
453
454 return G_SOURCE_CONTINUE;
455 }
456
457 int
main(int argc,char ** argv)458 main (int argc,
459 char **argv)
460 {
461 State *state = NULL;
462 GOptionContext *context = NULL;
463 static char **args = NULL;
464 gboolean debug = FALSE;
465 gboolean ret;
466 int exit_status = EX_OK;
467 static GOptionEntry entries [] = {
468 { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, "", "" },
469 { NULL }
470 };
471
472 bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
473 textdomain (GETTEXT_PACKAGE);
474 setlocale (LC_ALL, "");
475
476 gdm_log_init ();
477
478 context = g_option_context_new (_("GNOME Display Manager Wayland Session Launcher"));
479 g_option_context_add_main_entries (context, entries, NULL);
480
481 g_option_context_parse (context, &argc, &argv, NULL);
482 g_option_context_free (context);
483
484 if (args == NULL || args[0] == NULL || args[1] != NULL) {
485 g_warning ("gdm-wayland-session takes one argument (the session)");
486 exit_status = EX_USAGE;
487 goto out;
488 }
489
490 init_state (&state);
491
492 state->session_command = args[0];
493
494 state->settings = gdm_settings_new ();
495 ret = gdm_settings_direct_init (state->settings, DATADIR "/gdm/gdm.schemas", "/");
496
497 if (!ret) {
498 g_printerr ("Unable to initialize settings\n");
499 exit_status = EX_DATAERR;
500 goto out;
501 }
502
503 gdm_settings_direct_get_boolean (GDM_KEY_DEBUG, &debug);
504 state->debug_enabled = debug;
505
506 gdm_log_set_debug (debug);
507
508 state->main_loop = g_main_loop_new (NULL, FALSE);
509 state->cancellable = g_cancellable_new ();
510
511 g_unix_signal_add (SIGTERM, (GSourceFunc) on_sigterm, state);
512
513 ret = spawn_bus (state, state->cancellable);
514
515 if (!ret) {
516 g_printerr ("Unable to run session message bus\n");
517 exit_status = EX_SOFTWARE;
518 goto out;
519 }
520
521 import_environment (state, state->cancellable);
522
523 ret = spawn_session (state, state->cancellable);
524
525 if (!ret) {
526 g_printerr ("Unable to run session\n");
527 exit_status = EX_SOFTWARE;
528 goto out;
529 }
530
531 ret = register_display (state, state->cancellable);
532
533 if (!ret) {
534 g_printerr ("Unable to register display with display manager\n");
535 exit_status = EX_SOFTWARE;
536 goto out;
537 }
538
539 g_main_loop_run (state->main_loop);
540
541 /* Only use exit status of session if we're here because it exit */
542
543 if (state->session_subprocess == NULL) {
544 exit_status = state->session_exit_status;
545 }
546
547 out:
548 if (state != NULL) {
549 signal_subprocesses (state);
550 wait_on_subprocesses (state);
551 clear_state (&state);
552 }
553
554 return exit_status;
555 }
556