1 /*
2  * Copyright (C) 2013-2021 Red Hat, Inc.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17  * 02111-1307, USA.
18  */
19 
20 #include "config.h"
21 
22 #include "backends/native/meta-device-pool-private.h"
23 
24 #include <fcntl.h>
25 #include <gio/gunixfdlist.h>
26 #include <sys/stat.h>
27 #include <sys/stat.h>
28 #include <sys/sysmacros.h>
29 #include <sys/types.h>
30 
31 #include "backends/native/meta-launcher.h"
32 #include "meta/util.h"
33 
34 #include "meta-dbus-login1.h"
35 
36 struct _MetaDeviceFile
37 {
38   MetaDevicePool *pool;
39 
40   grefcount ref_count;
41 
42   char *path;
43   int major;
44   int minor;
45   int fd;
46   MetaDeviceFileFlags flags;
47   uint32_t tags[META_DEVICE_FILE_N_TAGS];
48 };
49 
50 struct _MetaDevicePool
51 {
52   GObject parent;
53 
54   MetaDbusLogin1Session *session_proxy;
55 
56   GMutex mutex;
57 
58   GList *files;
59 };
60 
61 G_DEFINE_TYPE (MetaDevicePool, meta_device_pool, G_TYPE_OBJECT)
62 
63 static void
64 release_device_file (MetaDevicePool *pool,
65                      MetaDeviceFile *file);
66 
67 static MetaDeviceFile *
meta_device_file_new(MetaDevicePool * pool,const char * path,int major,int minor,int fd,MetaDeviceFileFlags flags)68 meta_device_file_new (MetaDevicePool      *pool,
69                       const char          *path,
70                       int                  major,
71                       int                  minor,
72                       int                  fd,
73                       MetaDeviceFileFlags  flags)
74 {
75   MetaDeviceFile *file;
76 
77   file = g_new0 (MetaDeviceFile, 1);
78 
79   file->pool = pool;
80   g_ref_count_init (&file->ref_count);
81 
82   file->path = g_strdup (path);
83   file->major = major;
84   file->minor = minor;
85   file->fd = fd;
86   file->flags = flags;
87 
88   return file;
89 }
90 
91 static void
meta_device_file_free(MetaDeviceFile * file)92 meta_device_file_free (MetaDeviceFile *file)
93 {
94   g_free (file->path);
95   g_free (file);
96 }
97 
98 int
meta_device_file_get_fd(MetaDeviceFile * device_file)99 meta_device_file_get_fd (MetaDeviceFile *device_file)
100 {
101   g_assert (!g_ref_count_compare (&device_file->ref_count, 0));
102 
103   return device_file->fd;
104 }
105 
106 const char *
meta_device_file_get_path(MetaDeviceFile * device_file)107 meta_device_file_get_path (MetaDeviceFile *device_file)
108 {
109   return device_file->path;
110 }
111 
112 void
meta_device_file_tag(MetaDeviceFile * device_file,MetaDeviceFileTags tag,uint32_t value)113 meta_device_file_tag (MetaDeviceFile     *device_file,
114                       MetaDeviceFileTags  tag,
115                       uint32_t            value)
116 {
117   device_file->tags[tag] |= value;
118 }
119 
120 uint32_t
meta_device_file_has_tag(MetaDeviceFile * device_file,MetaDeviceFileTags tag,uint32_t value)121 meta_device_file_has_tag (MetaDeviceFile     *device_file,
122                           MetaDeviceFileTags  tag,
123                           uint32_t            value)
124 {
125   return (device_file->tags[tag] & value) == value;
126 }
127 
128 static MetaDeviceFile *
meta_device_file_acquire_locked(MetaDeviceFile * file)129 meta_device_file_acquire_locked (MetaDeviceFile *file)
130 {
131   g_ref_count_inc (&file->ref_count);
132   return file;
133 }
134 
135 MetaDeviceFile *
meta_device_file_acquire(MetaDeviceFile * file)136 meta_device_file_acquire (MetaDeviceFile *file)
137 {
138   g_mutex_lock (&file->pool->mutex);
139   meta_topic (META_DEBUG_BACKEND, "Acquiring device file '%s'", file->path);
140   meta_device_file_acquire_locked (file);
141   g_mutex_unlock (&file->pool->mutex);
142 
143   return file;
144 }
145 
146 void
meta_device_file_release(MetaDeviceFile * file)147 meta_device_file_release (MetaDeviceFile *file)
148 {
149   g_warn_if_fail (file->fd != -1);
150 
151   release_device_file (file->pool, file);
152 }
153 
154 MetaDevicePool *
meta_device_file_get_pool(MetaDeviceFile * device_file)155 meta_device_file_get_pool (MetaDeviceFile *device_file)
156 {
157   return device_file->pool;
158 }
159 
160 static MetaDeviceFile *
find_device_file_from_path(MetaDevicePool * pool,const char * path)161 find_device_file_from_path (MetaDevicePool *pool,
162                             const char     *path)
163 {
164   GList *l;
165 
166   for (l = pool->files; l; l = l->next)
167     {
168       MetaDeviceFile *file = l->data;
169 
170       if (g_strcmp0 (file->path, path) == 0)
171         return file;
172     }
173 
174   return NULL;
175 }
176 
177 static gboolean
take_device(MetaDbusLogin1Session * session_proxy,int dev_major,int dev_minor,int * out_fd,GCancellable * cancellable,GError ** error)178 take_device (MetaDbusLogin1Session  *session_proxy,
179              int                     dev_major,
180              int                     dev_minor,
181              int                    *out_fd,
182              GCancellable           *cancellable,
183              GError                **error)
184 {
185   g_autoptr (GVariant) fd_variant = NULL;
186   g_autoptr (GUnixFDList) fd_list = NULL;
187   int fd = -1;
188 
189   if (!meta_dbus_login1_session_call_take_device_sync (session_proxy,
190                                                        dev_major,
191                                                        dev_minor,
192                                                        NULL,
193                                                        &fd_variant,
194                                                        NULL, /* paused */
195                                                        &fd_list,
196                                                        cancellable,
197                                                        error))
198     return FALSE;
199 
200   fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (fd_variant), error);
201   if (fd == -1)
202     return FALSE;
203 
204   *out_fd = fd;
205   return TRUE;
206 }
207 
208 static gboolean
get_device_info_from_path(const char * path,int * out_major,int * out_minor)209 get_device_info_from_path (const char *path,
210                            int        *out_major,
211                            int        *out_minor)
212 {
213   int ret;
214   struct stat st;
215 
216   ret = stat (path, &st);
217   if (ret < 0 || !S_ISCHR (st.st_mode))
218     return FALSE;
219 
220   *out_major = major (st.st_rdev);
221   *out_minor = minor (st.st_rdev);
222   return TRUE;
223 }
224 
225 MetaDeviceFile *
meta_device_pool_open(MetaDevicePool * pool,const char * path,MetaDeviceFileFlags flags,GError ** error)226 meta_device_pool_open (MetaDevicePool       *pool,
227                        const char           *path,
228                        MetaDeviceFileFlags   flags,
229                        GError              **error)
230 {
231   g_autoptr (GMutexLocker) locker = NULL;
232   MetaDeviceFile *file;
233   int major = -1, minor = -1;
234   int fd;
235 
236   locker = g_mutex_locker_new (&pool->mutex);
237 
238   file = find_device_file_from_path (pool, path);
239   if (file)
240     {
241       g_warn_if_fail (file->flags == flags);
242       meta_device_file_acquire_locked (file);
243       return file;
244     }
245 
246   if (flags & META_DEVICE_FILE_FLAG_TAKE_CONTROL)
247     {
248       meta_topic (META_DEBUG_BACKEND,
249                   "Opening and taking control of device file '%s'",
250                   path);
251 
252       if (!pool->session_proxy)
253         {
254           g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
255                        "Can't take control without logind session");
256           return NULL;
257         }
258 
259       if (!get_device_info_from_path (path, &major, &minor))
260         {
261           g_set_error (error,
262                        G_IO_ERROR,
263                        G_IO_ERROR_NOT_FOUND,
264                        "Could not get device info for path %s: %m", path);
265           return NULL;
266         }
267 
268       if (!take_device (pool->session_proxy, major, minor, &fd, NULL, error))
269         return NULL;
270     }
271   else
272     {
273       int open_flags;
274 
275       meta_topic (META_DEBUG_BACKEND,
276                   "Opening device file '%s'",
277                   path);
278 
279       if (flags & META_DEVICE_FILE_FLAG_READ_ONLY)
280         open_flags = O_RDONLY;
281       else
282         open_flags = O_RDWR;
283       open_flags |= O_CLOEXEC;
284 
285       do
286         {
287           fd = open (path, open_flags);
288         }
289       while (fd == -1 && errno == EINTR);
290 
291       if (fd == -1)
292         {
293           g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
294                        "Failed to open device '%s': %s",
295                        path, g_strerror (errno));
296           return NULL;
297         }
298     }
299 
300   file = meta_device_file_new (pool, path, major, minor, fd, flags);
301   pool->files = g_list_prepend (pool->files, file);
302 
303   return file;
304 }
305 
306 static void
release_device_file(MetaDevicePool * pool,MetaDeviceFile * file)307 release_device_file (MetaDevicePool *pool,
308                      MetaDeviceFile *file)
309 {
310   g_autoptr (GMutexLocker) locker = NULL;
311   g_autoptr (GError) error = NULL;
312 
313   locker = g_mutex_locker_new (&pool->mutex);
314 
315   meta_topic (META_DEBUG_BACKEND, "Releasing device file '%s'", file->path);
316 
317   if (!g_ref_count_dec (&file->ref_count))
318     return;
319 
320   pool->files = g_list_remove (pool->files, file);
321 
322   if (file->flags & META_DEVICE_FILE_FLAG_TAKE_CONTROL)
323     {
324       MetaDbusLogin1Session *session_proxy;
325 
326       meta_topic (META_DEBUG_BACKEND,
327                   "Releasing control of and closing device file '%s'",
328                   file->path);
329 
330       session_proxy = pool->session_proxy;
331       if (!meta_dbus_login1_session_call_release_device_sync (session_proxy,
332                                                               file->major,
333                                                               file->minor,
334                                                               NULL, &error))
335         {
336           g_warning ("Could not release device '%s' (%d,%d): %s",
337                      file->path,
338                      file->major, file->minor,
339                      error->message);
340         }
341     }
342   else
343     {
344       meta_topic (META_DEBUG_BACKEND,
345                   "Closing device file '%s'",
346                   file->path);
347     }
348 
349   close (file->fd);
350 
351   meta_device_file_free (file);
352 }
353 
354 MetaDevicePool *
meta_device_pool_new(MetaLauncher * launcher)355 meta_device_pool_new (MetaLauncher *launcher)
356 {
357   MetaDevicePool *pool;
358 
359   pool = g_object_new (META_TYPE_DEVICE_POOL, NULL);
360 
361   if (launcher)
362     pool->session_proxy = meta_launcher_get_session_proxy (launcher);
363 
364   return pool;
365 }
366 
367 static void
meta_device_pool_finalize(GObject * object)368 meta_device_pool_finalize (GObject *object)
369 {
370   MetaDevicePool *pool = META_DEVICE_POOL (object);
371 
372   g_mutex_clear (&pool->mutex);
373   g_warn_if_fail (!pool->files);
374 
375   G_OBJECT_CLASS (meta_device_pool_parent_class)->finalize (object);
376 }
377 
378 static void
meta_device_pool_init(MetaDevicePool * pool)379 meta_device_pool_init (MetaDevicePool *pool)
380 {
381   g_mutex_init (&pool->mutex);
382 }
383 
384 static void
meta_device_pool_class_init(MetaDevicePoolClass * klass)385 meta_device_pool_class_init (MetaDevicePoolClass *klass)
386 {
387   GObjectClass *object_class = G_OBJECT_CLASS (klass);
388 
389   object_class->finalize = meta_device_pool_finalize;
390 }
391