mutter/src/backends/native/meta-device-pool.c

344 lines
8.3 KiB
C
Raw Normal View History

/*
* Copyright (C) 2013-2021 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 "config.h"
#include "backends/native/meta-device-pool-private.h"
#include <fcntl.h>
#include <gio/gunixfdlist.h>
#include <sys/stat.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include "backends/native/meta-launcher.h"
#include "meta-dbus-login1.h"
struct _MetaDeviceFile
{
MetaDevicePool *pool;
grefcount ref_count;
char *path;
int major;
int minor;
int fd;
MetaDeviceFileFlags flags;
};
struct _MetaDevicePool
{
GObject parent;
MetaDbusLogin1Session *session_proxy;
GMutex mutex;
GList *files;
};
G_DEFINE_TYPE (MetaDevicePool, meta_device_pool, G_TYPE_OBJECT)
static void
release_device_file (MetaDevicePool *pool,
MetaDeviceFile *file);
static MetaDeviceFile *
meta_device_file_new (MetaDevicePool *pool,
const char *path,
int major,
int minor,
int fd,
MetaDeviceFileFlags flags)
{
MetaDeviceFile *file;
file = g_new0 (MetaDeviceFile, 1);
file->pool = pool;
g_ref_count_init (&file->ref_count);
file->path = g_strdup (path);
file->major = major;
file->minor = minor;
file->fd = fd;
file->flags = flags;
return file;
}
static void
meta_device_file_free (MetaDeviceFile *file)
{
g_free (file->path);
g_free (file);
}
int
meta_device_file_get_fd (MetaDeviceFile *device_file)
{
g_assert (!g_ref_count_compare (&device_file->ref_count, 0));
return device_file->fd;
}
const char *
meta_device_file_get_path (MetaDeviceFile *device_file)
{
return device_file->path;
}
static MetaDeviceFile *
meta_device_file_acquire_locked (MetaDeviceFile *file)
{
g_ref_count_inc (&file->ref_count);
return file;
}
MetaDeviceFile *
meta_device_file_acquire (MetaDeviceFile *file)
{
g_mutex_lock (&file->pool->mutex);
meta_device_file_acquire_locked (file);
g_mutex_unlock (&file->pool->mutex);
return file;
}
void
meta_device_file_release (MetaDeviceFile *file)
{
g_warn_if_fail (file->fd != -1);
release_device_file (file->pool, file);
}
MetaDevicePool *
meta_device_file_get_pool (MetaDeviceFile *device_file)
{
return device_file->pool;
}
static MetaDeviceFile *
find_device_file_from_path (MetaDevicePool *pool,
const char *path)
{
GList *l;
for (l = pool->files; l; l = l->next)
{
MetaDeviceFile *file = l->data;
if (g_strcmp0 (file->path, path) == 0)
return file;
}
return NULL;
}
static gboolean
take_device (MetaDbusLogin1Session *session_proxy,
int dev_major,
int dev_minor,
int *out_fd,
GCancellable *cancellable,
GError **error)
{
g_autoptr (GVariant) fd_variant = NULL;
g_autoptr (GUnixFDList) fd_list = NULL;
int fd = -1;
if (!meta_dbus_login1_session_call_take_device_sync (session_proxy,
dev_major,
dev_minor,
NULL,
&fd_variant,
NULL, /* paused */
&fd_list,
cancellable,
error))
return FALSE;
fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (fd_variant), error);
if (fd == -1)
return FALSE;
*out_fd = fd;
return TRUE;
}
static gboolean
get_device_info_from_path (const char *path,
int *out_major,
int *out_minor)
{
int ret;
struct stat st;
ret = stat (path, &st);
if (ret < 0 || !S_ISCHR (st.st_mode))
return FALSE;
*out_major = major (st.st_rdev);
*out_minor = minor (st.st_rdev);
return TRUE;
}
MetaDeviceFile *
meta_device_pool_open (MetaDevicePool *pool,
const char *path,
MetaDeviceFileFlags flags,
GError **error)
{
g_autoptr (GMutexLocker) locker = NULL;
MetaDeviceFile *file;
int major = -1, minor = -1;
int fd;
locker = g_mutex_locker_new (&pool->mutex);
file = find_device_file_from_path (pool, path);
if (file)
{
g_warn_if_fail (file->flags == flags);
meta_device_file_acquire_locked (file);
return file;
}
if (flags & META_DEVICE_FILE_FLAG_TAKE_CONTROL)
{
if (!pool->session_proxy)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Can't take control without logind session");
return NULL;
}
if (!get_device_info_from_path (path, &major, &minor))
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"Could not get device info for path %s: %m", path);
return NULL;
}
if (!take_device (pool->session_proxy, major, minor, &fd, NULL, error))
return NULL;
}
else
{
do
{
fd = open (path, O_RDWR | O_CLOEXEC);
}
while (fd == -1 && errno == EINTR);
if (fd == -1)
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
"Failed to open device '%s': %s",
path, g_strerror (errno));
return NULL;
}
}
file = meta_device_file_new (pool, path, major, minor, fd, flags);
pool->files = g_list_prepend (pool->files, file);
return file;
}
static void
release_device_file (MetaDevicePool *pool,
MetaDeviceFile *file)
{
g_autoptr (GMutexLocker) locker = NULL;
g_autoptr (GError) error = NULL;
locker = g_mutex_locker_new (&pool->mutex);
if (!g_ref_count_dec (&file->ref_count))
return;
pool->files = g_list_remove (pool->files, file);
if (file->flags & META_DEVICE_FILE_FLAG_TAKE_CONTROL)
{
MetaDbusLogin1Session *session_proxy;
session_proxy = pool->session_proxy;
if (!meta_dbus_login1_session_call_release_device_sync (session_proxy,
file->major,
file->minor,
NULL, &error))
{
g_warning ("Could not release device '%s' (%d,%d): %s",
file->path,
file->major, file->minor,
error->message);
}
}
close (file->fd);
meta_device_file_free (file);
}
MetaDevicePool *
meta_device_pool_new (MetaLauncher *launcher)
{
MetaDevicePool *pool;
pool = g_object_new (META_TYPE_DEVICE_POOL, NULL);
if (launcher)
pool->session_proxy = meta_launcher_get_session_proxy (launcher);
return pool;
}
static void
meta_device_pool_finalize (GObject *object)
{
MetaDevicePool *pool = META_DEVICE_POOL (object);
g_mutex_clear (&pool->mutex);
g_warn_if_fail (!pool->files);
G_OBJECT_CLASS (meta_device_pool_parent_class)->finalize (object);
}
static void
meta_device_pool_init (MetaDevicePool *pool)
{
g_mutex_init (&pool->mutex);
}
static void
meta_device_pool_class_init (MetaDevicePoolClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = meta_device_pool_finalize;
}