diff --git a/configure.ac b/configure.ac index fd3c45579..3f997b126 100644 --- a/configure.ac +++ b/configure.ac @@ -138,6 +138,7 @@ AM_GLIB_GNU_GETTEXT ## here we get the flags we'll actually use # GRegex requires Glib-2.14.0 PKG_CHECK_MODULES(ALL, glib-2.0 >= 2.14.0) +PKG_CHECK_MODULES(MUTTER_LAUNCH, libdrm libsystemd-login) # Unconditionally use this dir to avoid a circular dep with gnomecc GNOME_KEYBINDINGS_KEYSDIR="${datadir}/gnome-control-center/keybindings" @@ -232,7 +233,7 @@ if test x$enable_wayland = "xyes"; then AC_SUBST(XWAYLAND_PATH) - MUTTER_PC_MODULES="$MUTTER_PC_MODULES wayland-server" + MUTTER_PC_MODULES="$MUTTER_PC_MODULES wayland-server libdrm" AC_DEFINE(HAVE_WAYLAND, , [Building with Wayland support]) have_wayland=yes fi diff --git a/src/Makefile.am b/src/Makefile.am index 1c0ac255a..941651d56 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -173,6 +173,8 @@ libmutter_la_SOURCES = \ if HAVE_WAYLAND libmutter_la_SOURCES += \ + wayland/meta-tty.c \ + wayland/meta-tty.h \ wayland/meta-wayland.c \ wayland/meta-wayland-private.h \ wayland/meta-xwayland-private.h \ @@ -186,7 +188,9 @@ libmutter_la_SOURCES += \ wayland/meta-wayland-seat.c \ wayland/meta-wayland-seat.h \ wayland/meta-wayland-stage.h \ - wayland/meta-wayland-stage.c + wayland/meta-wayland-stage.c \ + wayland/meta-weston-launch.c \ + wayland/meta-weston-launch.h endif libmutter_la_LDFLAGS = -no-undefined @@ -237,6 +241,19 @@ bin_PROGRAMS=mutter mutter_SOURCES = core/mutter.c mutter_LDADD = $(MUTTER_LIBS) libmutter.la +if HAVE_WAYLAND +bin_PROGRAMS+=mutter-launch + +mutter_launch_SOURCES = wayland/weston-launch.c wayland/weston-launch.h + +mutter_launch_CFLAGS = $(MUTTER_LAUNCH_CFLAGS) +mutter_launch_LDFLAGS = $(MUTTER_LAUNCH_LIBS) -lpam + +install-exec-hook: + -chown root $(DESTDIR)$(bindir)/mutter-launch + -chmod u+s $(DESTDIR)$(bindir)/mutter-launch +endif + if HAVE_INTROSPECTION include $(INTROSPECTION_MAKEFILE) diff --git a/src/wayland/meta-tty.c b/src/wayland/meta-tty.c new file mode 100644 index 000000000..8aae4c707 --- /dev/null +++ b/src/wayland/meta-tty.c @@ -0,0 +1,399 @@ +/* + * Copyright © 2010 Intel Corporation + * 2013 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "meta-tty.h" +#include +#include + +/* Introduced in 2.6.38 */ +#ifndef K_OFF +#define K_OFF 0x04 +#endif + +struct _MetaTTYClass +{ + GObjectClass parent_class; +}; + +struct _MetaTTY +{ + GObject parent; + + int fd; + struct termios terminal_attributes; + + int input_source, vt_source; + int vt, starting_vt; + gboolean has_vt; + int kb_mode; +}; + +enum { + SIGNAL_ENTER, + SIGNAL_LEAVE, + SIGNAL_LAST +}; + +static int signals[SIGNAL_LAST]; + +static void meta_tty_initable_iface_init (GInitableIface *); + +G_DEFINE_TYPE_WITH_CODE (MetaTTY, meta_tty, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + meta_tty_initable_iface_init)); + +static int +vt_handler (gpointer user_data) +{ + MetaTTY *tty = user_data; + + if (tty->has_vt) + { + tty->has_vt = FALSE; + g_signal_emit (tty, signals[SIGNAL_LEAVE], 0); + + ioctl (tty->fd, VT_RELDISP, 1); + } + else + { + ioctl (tty->fd, VT_RELDISP, VT_ACKACQ); + + tty->has_vt = 1; + g_signal_emit (tty, signals[SIGNAL_ENTER], 0); + } + + return 1; +} + +static int +on_tty_input (int fd, + GIOCondition mask, + gpointer user_data) +{ + MetaTTY *tty = user_data; + + /* Ignore input to tty. We get keyboard events from evdev */ + tcflush(tty->fd, TCIFLUSH); + + return 1; +} + +static int +try_open_vt (MetaTTY *tty, + GError **error) +{ + int tty0, fd; + char filename[16]; + + tty0 = open ("/dev/tty0", O_WRONLY | O_CLOEXEC); + if (tty0 < 0) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + "Could not open tty0: %s", strerror (errno)); + return -1; + } + + if (ioctl (tty0, VT_OPENQRY, &tty->vt) < 0 || tty->vt == -1) { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + "Could not open tty0: %s", strerror (errno)); + close (tty0); + return -1; + } + + close (tty0); + snprintf (filename, sizeof filename, "/dev/tty%d", tty->vt); + g_debug("compositor: using new vt %s\n", filename); + fd = open (filename, O_RDWR | O_NOCTTY | O_CLOEXEC); + return fd; +} + +/* FIXME? */ +static int +tty_activate_vt (MetaTTY *tty, + int vt) +{ + return ioctl(tty->fd, VT_ACTIVATE, vt); +} + +static int +env_get_fd (const char *env) +{ + const char *value; + + value = g_getenv (env); + + if (value == NULL) + return -1; + else + return g_ascii_strtoll (env, NULL, 10); +} + +static gboolean +meta_tty_initable_init(GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + MetaTTY *tty = META_TTY (initable); + struct termios raw_attributes; + struct vt_mode mode = { 0 }; + int ret; + + struct stat buf; + struct vt_stat vts; + + tty->fd = env_get_fd ("WESTON_TTY_FD"); + if (tty->fd < 0) + tty->fd = STDIN_FILENO; + + if (fstat(tty->fd, &buf) == 0 && + major(buf.st_rdev) == TTY_MAJOR && + minor(buf.st_rdev) > 0) + { + if (tty->fd == STDIN_FILENO) + tty->fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 0); + tty->vt = minor(buf.st_rdev); + } + else + { + /* Fall back to try opening a new VT. This typically + * requires root. */ + tty->fd = try_open_vt(tty, error); + } + + if (tty->fd <= 0 && (!error || !*error)) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + "Could not open tty0: %s", strerror (errno)); + return FALSE; + } + + if (ioctl(tty->fd, VT_GETSTATE, &vts) == 0) + tty->starting_vt = vts.v_active; + else + tty->starting_vt = tty->vt; + + if (tty->starting_vt != tty->vt) + { + if (ioctl(tty->fd, VT_ACTIVATE, tty->vt) < 0 || + ioctl(tty->fd, VT_WAITACTIVE, tty->vt) < 0) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + "Failed to switch to new vt: %s", strerror (errno)); + goto err; + } + } + + if (tcgetattr(tty->fd, &tty->terminal_attributes) < 0) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + "Could not get terminal attributes: %s", strerror (errno)); + goto err; + } + + /* Ignore control characters and disable echo */ + raw_attributes = tty->terminal_attributes; + cfmakeraw(&raw_attributes); + + /* Fix up line endings to be normal (cfmakeraw hoses them) */ + raw_attributes.c_oflag |= OPOST | OCRNL; + /* Don't generate ttou signals */ + raw_attributes.c_oflag &= ~TOSTOP; + + if (tcsetattr(tty->fd, TCSANOW, &raw_attributes) < 0) + g_warning("Could not put terminal into raw mode: %s", strerror (errno)); + + ioctl(tty->fd, KDGKBMODE, &tty->kb_mode); + ret = ioctl(tty->fd, KDSKBMODE, K_OFF); + if (ret) + { + ret = ioctl(tty->fd, KDSKBMODE, K_RAW); + if (ret) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + "Failed to set keyboard mode: %s", strerror (errno)); + goto err_attr; + } + + tty->input_source = g_unix_fd_add (tty->fd, + G_IO_IN, + on_tty_input, tty); + } + + ret = ioctl(tty->fd, KDSETMODE, KD_GRAPHICS); + if (ret) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + "Failed to set KD_GRAPHICS mode: %s", strerror (errno)); + goto err_kdkbmode; + } + + tty->has_vt = 1; + mode.mode = VT_PROCESS; + mode.relsig = SIGUSR1; + mode.acqsig = SIGUSR1; + if (ioctl(tty->fd, VT_SETMODE, &mode) < 0) + { + g_set_error (error, G_IO_ERROR, + g_io_error_from_errno (errno), + "Failed to take control of vt handling: %s", strerror (errno)); + goto err_kdmode; + } + + tty->vt_source = g_unix_signal_add (SIGUSR1, vt_handler, tty); + + return TRUE; + + err_kdmode: + ioctl (tty->fd, KDSETMODE, KD_TEXT); + + err_kdkbmode: + if (tty->input_source) + g_source_remove (tty->input_source); + ioctl (tty->fd, KDSKBMODE, tty->kb_mode); + + err_attr: + tcsetattr (tty->fd, TCSANOW, &tty->terminal_attributes); + + err: + close (tty->fd); + return FALSE; +} + +static void +tty_reset (MetaTTY *tty) +{ + struct vt_mode mode = { 0 }; + + if (ioctl (tty->fd, KDSKBMODE, tty->kb_mode)) + g_warning ("failed to restore keyboard mode: %s", strerror (errno)); + + if (ioctl (tty->fd, KDSETMODE, KD_TEXT)) + g_warning ("failed to set KD_TEXT mode on tty: %s", strerror (errno)); + + if (tcsetattr (tty->fd, TCSANOW, &tty->terminal_attributes) < 0) + g_warning ("could not restore terminal to canonical mode"); + + mode.mode = VT_AUTO; + if (ioctl (tty->fd, VT_SETMODE, &mode) < 0) + g_warning ("could not reset vt handling\n"); + + if (tty->has_vt && tty->vt != tty->starting_vt) + { + ioctl(tty->fd, VT_ACTIVATE, tty->starting_vt); + ioctl(tty->fd, VT_WAITACTIVE, tty->starting_vt); + } +} + +static void +meta_tty_finalize (GObject *object) +{ + MetaTTY *tty = META_TTY (object); + + if (tty->input_source) + g_source_remove (tty->input_source); + + g_source_remove (tty->vt_source); + + tty_reset (tty); + + close (tty->fd); + + G_OBJECT_CLASS (meta_tty_parent_class)->finalize (object); +} + +static void +meta_tty_init (MetaTTY *self) +{ +} + +static void +meta_tty_class_init (MetaTTYClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = meta_tty_finalize; + + signals[SIGNAL_ENTER] = g_signal_new ("enter", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, /* class offset */ + NULL, NULL, /* accumulator */ + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[SIGNAL_LEAVE] = g_signal_new ("leave", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, /* class offset */ + NULL, NULL, /* accumulator */ + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +meta_tty_initable_iface_init (GInitableIface *iface) +{ + iface->init = meta_tty_initable_init; +} + +MetaTTY * +meta_tty_new (void) +{ + GError *error; + MetaTTY *tty; + + error = NULL; + tty = g_initable_new (META_TYPE_TTY, NULL, &error, NULL); + + if (tty == NULL) + { + g_warning ("Failed to initalize TTY handling: %s", error->message); + g_error_free (error); + } + + return tty; +} diff --git a/src/wayland/meta-tty.h b/src/wayland/meta-tty.h new file mode 100644 index 000000000..2c3bf2adc --- /dev/null +++ b/src/wayland/meta-tty.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_TTY_H +#define META_TTY_H + +#include + +G_BEGIN_DECLS + +#define META_TYPE_TTY (meta_tty_get_type()) +#define META_TTY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), META_TYPE_TTY, MetaTTY)) +#define META_TTY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), META_TYPE_TTY, MetaTTYClass)) +#define META_IS_TTY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), META_TYPE_TTY)) +#define META_IS_TTY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), META_TYPE_TTY)) +#define META_TTY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), META_TTY, MetaTTYClass)) + +typedef struct _MetaTTY MetaTTY; +typedef struct _MetaTTYClass MetaTTYClass; + +GType meta_tty_get_type (void) G_GNUC_CONST; + +MetaTTY *meta_tty_new (void); + +G_END_DECLS + +#endif /* META_TTY_H */ diff --git a/src/wayland/meta-wayland-private.h b/src/wayland/meta-wayland-private.h index a859378c1..7d111ec42 100644 --- a/src/wayland/meta-wayland-private.h +++ b/src/wayland/meta-wayland-private.h @@ -28,6 +28,7 @@ #include #include "window-private.h" +#include "meta-tty.h" typedef struct _MetaWaylandCompositor MetaWaylandCompositor; @@ -165,6 +166,10 @@ struct _MetaWaylandCompositor struct wl_client *xwayland_client; struct wl_resource *xserver_resource; + MetaTTY *tty; + int drm_fd; + GSocket *weston_launch; + MetaWaylandSeat *seat; /* This surface is only used to keep drag of the implicit grab when diff --git a/src/wayland/meta-wayland.c b/src/wayland/meta-wayland.c index 5b62e8967..035fc22ed 100644 --- a/src/wayland/meta-wayland.c +++ b/src/wayland/meta-wayland.c @@ -48,6 +48,7 @@ #include #include #include "frame.h" +#include "meta-weston-launch.h" static MetaWaylandCompositor _meta_wayland_compositor; @@ -1379,11 +1380,59 @@ event_emission_hook_cb (GSignalInvocationHint *ihint, return TRUE /* stay connected */; } +static int +env_get_fd (const char *env) +{ + const char *value; + + value = g_getenv (env); + + if (value == NULL) + return -1; + else + return g_ascii_strtoll (env, NULL, 10); +} + +static void +on_our_vt_enter (MetaTTY *tty, + MetaWaylandCompositor *compositor) +{ + GError *error; + + error = NULL; + if (!meta_weston_launch_set_master (compositor->weston_launch, + compositor->drm_fd, TRUE, &error)) + { + g_warning ("Failed to become DRM master: %s", error->message); + g_error_free (error); + } +} + +static void +on_our_vt_leave (MetaTTY *tty, + MetaWaylandCompositor *compositor) +{ + GError *error; + + error = NULL; + if (!meta_weston_launch_set_master (compositor->weston_launch, + compositor->drm_fd, FALSE, &error)) + { + g_warning ("Failed to release DRM master: %s", error->message); + g_error_free (error); + } + + /* FIXME: we must release input devices as well! */ +} + void meta_wayland_init (void) { MetaWaylandCompositor *compositor = &_meta_wayland_compositor; guint event_signal; + ClutterBackend *backend; + CoglContext *cogl_context; + CoglRenderer *cogl_renderer; memset (compositor, 0, sizeof (MetaWaylandCompositor)); @@ -1423,6 +1472,34 @@ meta_wayland_init (void) if (clutter_init (NULL, NULL) != CLUTTER_INIT_SUCCESS) g_error ("Failed to initialize Clutter"); + backend = clutter_get_default_backend (); + cogl_context = clutter_backend_get_cogl_context (backend); + cogl_renderer = cogl_display_get_renderer (cogl_context_get_display (cogl_context)); + + if (cogl_renderer_get_winsys_id (cogl_renderer) == COGL_WINSYS_ID_EGL_KMS) + compositor->drm_fd = cogl_kms_renderer_get_kms_fd (cogl_renderer); + else + compositor->drm_fd = -1; + + if (compositor->drm_fd >= 0) + { + /* Running on bare metal, let's initalize DRM master and VT handling */ + int weston_launch_fd; + + weston_launch_fd = env_get_fd ("WESTON_LAUNCHER_SOCK"); + if (weston_launch_fd >= 0) + compositor->weston_launch = g_socket_new_from_fd (weston_launch_fd, NULL); + + compositor->tty = meta_tty_new (); + if (compositor->tty) + { + g_signal_connect (compositor->tty, "enter", G_CALLBACK (on_our_vt_enter), compositor); + g_signal_connect (compositor->tty, "leave", G_CALLBACK (on_our_vt_leave), compositor); + } + + on_our_vt_enter (compositor->tty, compositor); + } + compositor->stage = meta_wayland_stage_new (); clutter_stage_set_user_resizable (CLUTTER_STAGE (compositor->stage), FALSE); g_signal_connect_after (compositor->stage, "paint", @@ -1480,5 +1557,10 @@ meta_wayland_init (void) void meta_wayland_finalize (void) { - meta_xwayland_stop (meta_wayland_compositor_get_default ()); + MetaWaylandCompositor *compositor; + + compositor = meta_wayland_compositor_get_default (); + + meta_xwayland_stop (compositor); + g_clear_object (&compositor->tty); } diff --git a/src/wayland/meta-weston-launch.c b/src/wayland/meta-weston-launch.c new file mode 100644 index 000000000..68f14cf88 --- /dev/null +++ b/src/wayland/meta-weston-launch.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "meta-weston-launch.h" + +static gboolean +send_message_to_wl (GSocket *weston_launch, + void *message, + gsize size, + GSocketControlMessage *out_cmsg, + GSocketControlMessage **in_cmsg, + GError **error) +{ + int ok; + GInputVector in_iov = { &ok, sizeof (int) }; + GOutputVector out_iov = { message, size }; + GSocketControlMessage *out_all_cmsg[2]; + GSocketControlMessage **in_all_cmsg; + int flags = 0; + int i; + + out_all_cmsg[0] = out_cmsg; + out_all_cmsg[1] = NULL; + if (g_socket_send_message (weston_launch, NULL, + &out_iov, 1, + out_all_cmsg, -1, + flags, NULL, error) != (gssize)size) + return FALSE; + + if (g_socket_receive_message (weston_launch, NULL, + &in_iov, 1, + &in_all_cmsg, NULL, + &flags, NULL, error) != sizeof (int)) + return FALSE; + + if (ok != 0) + { + if (ok == -1) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Got failure from weston-launch"); + else + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ok), + "Got failure from weston-launch: %s", strerror (-ok)); + + for (i = 0; in_all_cmsg[i]; i++) + g_object_unref (in_all_cmsg[i]); + g_free (in_all_cmsg); + + return FALSE; + } + + if (in_all_cmsg[0]) + { + for (i = 1; in_all_cmsg[i]; i++) + g_object_unref (in_all_cmsg[i]); + *in_cmsg = in_all_cmsg[0]; + } + + g_free (in_all_cmsg); + return TRUE; +} + +gboolean +meta_weston_launch_set_master (GSocket *weston_launch, + int drm_fd, + gboolean master, + GError **error) +{ + if (weston_launch) + { + struct weston_launcher_set_master message; + GSocketControlMessage *cmsg; + gboolean ok; + + message.header.opcode = WESTON_LAUNCHER_DRM_SET_MASTER; + message.set_master = master; + + cmsg = g_unix_fd_message_new (); + if (g_unix_fd_message_append_fd (G_UNIX_FD_MESSAGE (cmsg), + drm_fd, error) == FALSE) + { + g_object_unref (cmsg); + return FALSE; + } + + ok = send_message_to_wl (weston_launch, &message, sizeof message, cmsg, NULL, error); + + g_object_unref (cmsg); + return ok; + } + else + { + int ret; + + if (master) + ret = drmSetMaster (drm_fd); + else + ret = drmDropMaster (drm_fd); + + if (ret < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret), + "Failed to set DRM master directly: %s", strerror (-ret)); + return FALSE; + } + else + return TRUE; + } +} + +int +meta_weston_launch_open_input_device (GSocket *weston_launch, + const char *name, + int flags, + GError **error) +{ + if (weston_launch) + { + struct weston_launcher_open *message; + GSocketControlMessage *cmsg; + gboolean ok; + int *fds, n_fd; + int ret; + + message = g_malloc (sizeof (struct weston_launcher_open) + + strlen (name)); + message->header.opcode = WESTON_LAUNCHER_OPEN; + message->flags = flags; + strcpy (message->path, name); + + ok = send_message_to_wl (weston_launch, message, + sizeof (struct weston_launcher_open) + strlen (name), + NULL, &cmsg, error); + + if (ok) + { + g_assert (G_IS_UNIX_FD_MESSAGE (cmsg)); + + fds = g_unix_fd_message_steal_fds (G_UNIX_FD_MESSAGE (cmsg), &n_fd); + g_assert (n_fd == 1); + + ret = fds[0]; + g_free (fds); + } + else + ret = -1; + + g_free (message); + g_object_unref (cmsg); + return ret; + } + else + { + int ret; + + ret = open (name, flags, 0); + + if (ret < 0) + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Failed to open input device directly: %s", strerror (errno)); + + return ret; + } +} + diff --git a/src/wayland/meta-weston-launch.h b/src/wayland/meta-weston-launch.h new file mode 100644 index 000000000..f642ba9d1 --- /dev/null +++ b/src/wayland/meta-weston-launch.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef META_WESTON_LAUNCH_H +#define META_WESTON_LAUNCH_H + +#include + +/* Keep this in sync with weston-launch */ + +enum weston_launcher_opcode { + WESTON_LAUNCHER_OPEN, + WESTON_LAUNCHER_DRM_SET_MASTER +}; + +struct weston_launcher_message { + int opcode; +}; + +struct weston_launcher_open { + struct weston_launcher_message header; + int flags; + char path[0]; +}; + +struct weston_launcher_set_master { + struct weston_launcher_message header; + int set_master; +}; + +gboolean meta_weston_launch_set_master (GSocket *weston_launch, + int drm_fd, + gboolean master, + GError **error); +int meta_weston_launch_open_input_device (GSocket *weston_launch, + const char *name, + int flags, + GError **error); + +#endif diff --git a/src/wayland/weston-launch.c b/src/wayland/weston-launch.c new file mode 100644 index 000000000..b38386eca --- /dev/null +++ b/src/wayland/weston-launch.c @@ -0,0 +1,703 @@ +/* + * Copyright © 2012 Benjamin Franzke + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef HAVE_SYSTEMD_LOGIN +#include +#endif + +#include "weston-launch.h" + +#define MAX_ARGV_SIZE 256 + +struct weston_launch { + struct pam_conv pc; + pam_handle_t *ph; + int tty; + int ttynr; + int sock[2]; + struct passwd *pw; + + int signalfd; + + pid_t child; + int verbose; + char *new_user; +}; + +union cmsg_data { unsigned char b[4]; int fd; }; + +static gid_t * +read_groups(void) +{ + int n; + gid_t *groups; + + n = getgroups(0, NULL); + + if (n < 0) { + fprintf(stderr, "Unable to retrieve groups: %m\n"); + return NULL; + } + + groups = malloc(n * sizeof(gid_t)); + if (!groups) + return NULL; + + if (getgroups(n, groups) < 0) { + fprintf(stderr, "Unable to retrieve groups: %m\n"); + free(groups); + return NULL; + } + return groups; +} + +static int +weston_launch_allowed(struct weston_launch *wl) +{ + struct group *gr; + gid_t *groups; + int i; +#ifdef HAVE_SYSTEMD_LOGIN + char *session, *seat; + int err; +#endif + + if (getuid() == 0) + return 1; + + gr = getgrnam("weston-launch"); + if (gr) { + groups = read_groups(); + if (groups) { + for (i = 0; groups[i]; ++i) { + if (groups[i] == gr->gr_gid) { + free(groups); + return 1; + } + } + free(groups); + } + } + +#ifdef HAVE_SYSTEMD_LOGIN + err = sd_pid_get_session(getpid(), &session); + if (err == 0 && session) { + if (sd_session_is_active(session) && + sd_session_get_seat(session, &seat) == 0) { + free(seat); + free(session); + return 1; + } + free(session); + } +#endif + + return 0; +} + +static int +pam_conversation_fn(int msg_count, + const struct pam_message **messages, + struct pam_response **responses, + void *user_data) +{ + return PAM_SUCCESS; +} + +static int +setup_pam(struct weston_launch *wl) +{ + int err; + + wl->pc.conv = pam_conversation_fn; + wl->pc.appdata_ptr = wl; + + err = pam_start("login", wl->pw->pw_name, &wl->pc, &wl->ph); + if (err != PAM_SUCCESS) { + fprintf(stderr, "failed to start pam transaction: %d: %s\n", + err, pam_strerror(wl->ph, err)); + return -1; + } + + err = pam_set_item(wl->ph, PAM_TTY, ttyname(wl->tty)); + if (err != PAM_SUCCESS) { + fprintf(stderr, "failed to set PAM_TTY item: %d: %s\n", + err, pam_strerror(wl->ph, err)); + return -1; + } + + err = pam_open_session(wl->ph, 0); + if (err != PAM_SUCCESS) { + fprintf(stderr, "failed to open pam session: %d: %s\n", + err, pam_strerror(wl->ph, err)); + return -1; + } + + return 0; +} + +static int +setup_launcher_socket(struct weston_launch *wl) +{ + if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, wl->sock) < 0) + error(1, errno, "socketpair failed"); + + fcntl(wl->sock[0], F_SETFD, O_CLOEXEC); + + return 0; +} + +static int +setup_signals(struct weston_launch *wl) +{ + int ret; + sigset_t mask; + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sa.sa_handler = SIG_DFL; + sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; + ret = sigaction(SIGCHLD, &sa, NULL); + assert(ret == 0); + + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction(SIGHUP, &sa, NULL); + + ret = sigemptyset(&mask); + assert(ret == 0); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + ret = sigprocmask(SIG_BLOCK, &mask, NULL); + assert(ret == 0); + + wl->signalfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (wl->signalfd < 0) + return -errno; + + return 0; +} + +static void +setenv_fd(const char *env, int fd) +{ + char buf[32]; + + snprintf(buf, sizeof buf, "%d", fd); + setenv(env, buf, 1); +} + +static int +handle_setmaster(struct weston_launch *wl, struct msghdr *msg, ssize_t len) +{ + int ret = -1; + struct cmsghdr *cmsg; + struct weston_launcher_set_master *message; + union cmsg_data *data; + + if (len != sizeof(*message)) { + error(0, 0, "missing value in setmaster request"); + goto out; + } + + message = msg->msg_iov->iov_base; + + cmsg = CMSG_FIRSTHDR(msg); + if (!cmsg || + cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) { + error(0, 0, "invalid control message"); + goto out; + } + + data = (union cmsg_data *) CMSG_DATA(cmsg); + if (data->fd == -1) { + error(0, 0, "missing drm fd in socket request"); + goto out; + } + + if (message->set_master) + ret = drmSetMaster(data->fd); + else + ret = drmDropMaster(data->fd); + + close(data->fd); + + if (wl->verbose) + fprintf(stderr, "weston-launch: %sMaster, ret: %d, fd: %d\n", + message->set_master ? "set" : "drop", ret, data->fd); + +out: + do { + len = send(wl->sock[0], &ret, sizeof ret, 0); + } while (len < 0 && errno == EINTR); + if (len < 0) + return -1; + + return 0; +} + +static int +handle_open(struct weston_launch *wl, struct msghdr *msg, ssize_t len) +{ + int fd = -1, ret = -1; + char control[CMSG_SPACE(sizeof(fd))]; + struct cmsghdr *cmsg; + struct stat s; + struct msghdr nmsg; + struct iovec iov; + struct weston_launcher_open *message; + union cmsg_data *data; + + message = msg->msg_iov->iov_base; + if ((size_t)len < sizeof(*message)) + goto err0; + + /* Ensure path is null-terminated */ + ((char *) message)[len-1] = '\0'; + + if (stat(message->path, &s) < 0) + goto err0; + + fd = open(message->path, message->flags); + if (fd < 0) { + fprintf(stderr, "Error opening device %s: %m\n", + message->path); + goto err0; + } + + if (major(s.st_rdev) != INPUT_MAJOR) { + close(fd); + fd = -1; + fprintf(stderr, "Device %s is not an input device\n", + message->path); + goto err0; + } + +err0: + memset(&nmsg, 0, sizeof nmsg); + nmsg.msg_iov = &iov; + nmsg.msg_iovlen = 1; + if (fd != -1) { + nmsg.msg_control = control; + nmsg.msg_controllen = sizeof control; + cmsg = CMSG_FIRSTHDR(&nmsg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + data = (union cmsg_data *) CMSG_DATA(cmsg); + data->fd = fd; + nmsg.msg_controllen = cmsg->cmsg_len; + ret = 0; + } + iov.iov_base = &ret; + iov.iov_len = sizeof ret; + + if (wl->verbose) + fprintf(stderr, "weston-launch: opened %s: ret: %d, fd: %d\n", + message->path, ret, fd); + do { + len = sendmsg(wl->sock[0], &nmsg, 0); + } while (len < 0 && errno == EINTR); + + close(fd); + + if (len < 0) + return -1; + + return 0; +} + +static int +handle_socket_msg(struct weston_launch *wl) +{ + char control[CMSG_SPACE(sizeof(int))]; + char buf[BUFSIZ]; + struct msghdr msg; + struct iovec iov; + int ret = -1; + ssize_t len; + struct weston_launcher_message *message; + + memset(&msg, 0, sizeof(msg)); + iov.iov_base = buf; + iov.iov_len = sizeof buf; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof control; + + do { + len = recvmsg(wl->sock[0], &msg, 0); + } while (len < 0 && errno == EINTR); + + if (len < 1) + return -1; + + message = (void *) buf; + switch (message->opcode) { + case WESTON_LAUNCHER_OPEN: + ret = handle_open(wl, &msg, len); + break; + case WESTON_LAUNCHER_DRM_SET_MASTER: + ret = handle_setmaster(wl, &msg, len); + break; + } + + return ret; +} + +static void +quit(struct weston_launch *wl, int status) +{ + int err; + + close(wl->signalfd); + close(wl->sock[0]); + + if (wl->new_user) { + err = pam_close_session(wl->ph, 0); + if (err) + fprintf(stderr, "pam_close_session failed: %d: %s\n", + err, pam_strerror(wl->ph, err)); + pam_end(wl->ph, err); + } + + exit(status); +} + +static int +handle_signal(struct weston_launch *wl) +{ + struct signalfd_siginfo sig; + int pid, status, ret; + + if (read(wl->signalfd, &sig, sizeof sig) != sizeof sig) { + error(0, errno, "reading signalfd failed"); + return -1; + } + + switch (sig.ssi_signo) { + case SIGCHLD: + pid = waitpid(-1, &status, 0); + if (pid == wl->child) { + wl->child = 0; + if (WIFEXITED(status)) + ret = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + /* + * If weston dies because of signal N, we + * return 10+N. This is distinct from + * weston-launch dying because of a signal + * (128+N). + */ + ret = 10 + WTERMSIG(status); + else + ret = 0; + quit(wl, ret); + } + break; + case SIGTERM: + case SIGINT: + if (wl->child) + kill(wl->child, sig.ssi_signo); + break; + default: + return -1; + } + + return 0; +} + +static int +setup_tty(struct weston_launch *wl, const char *tty) +{ + struct stat buf; + char *t; + + if (!wl->new_user) { + wl->tty = STDIN_FILENO; + } else if (tty) { + t = ttyname(STDIN_FILENO); + if (t && strcmp(t, tty) == 0) + wl->tty = STDIN_FILENO; + else + wl->tty = open(tty, O_RDWR | O_NOCTTY); + } else { + int tty0 = open("/dev/tty0", O_WRONLY | O_CLOEXEC); + char filename[16]; + + if (tty0 < 0) + error(1, errno, "could not open tty0"); + + if (ioctl(tty0, VT_OPENQRY, &wl->ttynr) < 0 || wl->ttynr == -1) + error(1, errno, "failed to find non-opened console"); + + snprintf(filename, sizeof filename, "/dev/tty%d", wl->ttynr); + wl->tty = open(filename, O_RDWR | O_NOCTTY); + close(tty0); + } + + if (wl->tty < 0) + error(1, errno, "failed to open tty"); + + if (tty) { + if (fstat(wl->tty, &buf) < 0) + error(1, errno, "stat %s failed", tty); + + if (major(buf.st_rdev) != TTY_MAJOR) + error(1, 0, "invalid tty device: %s", tty); + + wl->ttynr = minor(buf.st_rdev); + } + + return 0; +} + +static void +setup_session(struct weston_launch *wl) +{ + char **env; + char *term; + int i; + + if (wl->tty != STDIN_FILENO) { + if (setsid() < 0) + error(1, errno, "setsid failed"); + if (ioctl(wl->tty, TIOCSCTTY, 0) < 0) + error(1, errno, "TIOCSCTTY failed - tty is in use"); + } + + if (setgid(wl->pw->pw_gid) < 0 || +#ifdef HAVE_INITGROUPS + initgroups(wl->pw->pw_name, wl->pw->pw_gid) < 0 || +#endif + setuid(wl->pw->pw_uid) < 0) + error(1, errno, "dropping privileges failed"); + + term = getenv("TERM"); + clearenv(); + setenv("TERM", term, 1); + setenv("USER", wl->pw->pw_name, 1); + setenv("LOGNAME", wl->pw->pw_name, 1); + setenv("HOME", wl->pw->pw_dir, 1); + setenv("SHELL", wl->pw->pw_shell, 1); + + env = pam_getenvlist(wl->ph); + if (env) { + for (i = 0; env[i]; ++i) { + if (putenv(env[i]) < 0) + error(0, 0, "putenv %s failed", env[i]); + } + free(env); + } +} + +static void +launch_compositor(struct weston_launch *wl, int argc, char *argv[]) +{ + char command[PATH_MAX]; + char *child_argv[MAX_ARGV_SIZE]; + sigset_t mask; + int i; + + if (wl->verbose) + printf("weston-launch: spawned weston with pid: %d\n", getpid()); + if (wl->new_user) + setup_session(wl); + + if (wl->tty != STDIN_FILENO) + setenv_fd("WESTON_TTY_FD", wl->tty); + + setenv_fd("WESTON_LAUNCHER_SOCK", wl->sock[1]); + + unsetenv("DISPLAY"); + + /* Do not give our signal mask to the new process. */ + sigemptyset(&mask); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGINT); + sigprocmask(SIG_UNBLOCK, &mask, NULL); + + snprintf (command, PATH_MAX, "%s \"$@\"", argv[0]); + + child_argv[0] = wl->pw->pw_shell; + child_argv[1] = "-l"; + child_argv[2] = "-c"; + child_argv[3] = command; + for (i = 0; i < argc; ++i) + child_argv[4 + i] = argv[i]; + child_argv[4 + i] = NULL; + + execv(child_argv[0], child_argv); + error(1, errno, "exec failed"); +} + +static void +help(const char *name) +{ + fprintf(stderr, "Usage: %s [args...] [-- [weston args..]]\n", name); + fprintf(stderr, " -u, --user Start session as specified username\n"); + fprintf(stderr, " -t, --tty Start session on alternative tty\n"); + fprintf(stderr, " -v, --verbose Be verbose\n"); + fprintf(stderr, " -h, --help Display this help message\n"); +} + +int +main(int argc, char *argv[]) +{ + struct weston_launch wl; + int i, c; + char *tty = NULL; + struct option opts[] = { + { "user", required_argument, NULL, 'u' }, + { "tty", required_argument, NULL, 't' }, + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { 0, 0, NULL, 0 } + }; + + memset(&wl, 0, sizeof wl); + + while ((c = getopt_long(argc, argv, "u:t::vh", opts, &i)) != -1) { + switch (c) { + case 'u': + wl.new_user = optarg; + if (getuid() != 0) + error(1, 0, "Permission denied. -u allowed for root only"); + break; + case 't': + tty = optarg; + break; + case 'v': + wl.verbose = 1; + break; + case 'h': + help("weston-launch"); + exit(EXIT_FAILURE); + } + } + + if ((argc - optind) > (MAX_ARGV_SIZE - 6)) + error(1, E2BIG, "Too many arguments to pass to weston"); + + if (strcmp (argv[optind], "mutter") && + strcmp (argv[optind], "gnome-shell") && + strcmp (argv[optind], "gnome-shell-real")) + error(1, 0, "mutter-launch can only be used to launch mutter or gnome-shell"); + + if (wl.new_user) + wl.pw = getpwnam(wl.new_user); + else + wl.pw = getpwuid(getuid()); + if (wl.pw == NULL) + error(1, errno, "failed to get username"); + + if (!weston_launch_allowed(&wl)) + error(1, 0, "Permission denied. You should either:\n" +#ifdef HAVE_SYSTEMD_LOGIN + " - run from an active and local (systemd) session.\n" +#else + " - enable systemd session support for weston-launch.\n" +#endif + " - or add yourself to the 'weston-launch' group."); + + if (setup_tty(&wl, tty) < 0) + exit(EXIT_FAILURE); + + if (wl.new_user && setup_pam(&wl) < 0) + exit(EXIT_FAILURE); + + if (setup_launcher_socket(&wl) < 0) + exit(EXIT_FAILURE); + + if (setup_signals(&wl) < 0) + exit(EXIT_FAILURE); + + wl.child = fork(); + if (wl.child == -1) { + error(1, errno, "fork failed"); + exit(EXIT_FAILURE); + } + + if (wl.child == 0) + launch_compositor(&wl, argc - optind, argv + optind); + + close(wl.sock[1]); + if (wl.tty != STDIN_FILENO) + close(wl.tty); + + while (1) { + struct pollfd fds[2]; + int n; + + fds[0].fd = wl.sock[0]; + fds[0].events = POLLIN; + fds[1].fd = wl.signalfd; + fds[1].events = POLLIN; + + n = poll(fds, 2, -1); + if (n < 0) + error(0, errno, "poll failed"); + if (fds[0].revents & POLLIN) + handle_socket_msg(&wl); + if (fds[1].revents) + handle_signal(&wl); + } + + return 0; +} diff --git a/src/wayland/weston-launch.h b/src/wayland/weston-launch.h new file mode 100644 index 000000000..5be013efc --- /dev/null +++ b/src/wayland/weston-launch.h @@ -0,0 +1,46 @@ +/* + * Copyright © 2012 Benjamin Franzke + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _WESTON_LAUNCH_H_ +#define _WESTON_LAUNCH_H_ + +enum weston_launcher_opcode { + WESTON_LAUNCHER_OPEN, + WESTON_LAUNCHER_DRM_SET_MASTER +}; + +struct weston_launcher_message { + int opcode; +}; + +struct weston_launcher_open { + struct weston_launcher_message header; + int flags; + char path[0]; +}; + +struct weston_launcher_set_master { + struct weston_launcher_message header; + int set_master; +}; + +#endif