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