]> git.sesse.net Git - vlc/blob - modules/services_discovery/udev.c
audioscrobbler: factorize test and fix potential memleak
[vlc] / modules / services_discovery / udev.c
1 /**
2  * @file udev.c
3  * @brief List of multimedia devices for VLC media player
4  */
5 /*****************************************************************************
6  * Copyright © 2009 Rémi Denis-Courmont
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <errno.h>
28 #include <search.h>
29 #include <poll.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32
33 #include <libudev.h>
34
35 #include <vlc_common.h>
36 #include <vlc_services_discovery.h>
37 #include <vlc_plugin.h>
38 #ifdef HAVE_ALSA
39 # include <vlc_modules.h>
40 #endif
41 #include <vlc_url.h>
42
43 static int OpenV4L (vlc_object_t *);
44 #ifdef HAVE_ALSA
45 static int OpenALSA (vlc_object_t *);
46 #endif
47 static int OpenDisc (vlc_object_t *);
48 static void Close (vlc_object_t *);
49 static int vlc_sd_probe_Open (vlc_object_t *);
50
51 /*
52  * Module descriptor
53  */
54 vlc_module_begin ()
55     set_shortname (N_("Video capture"))
56     set_description (N_("Video capture (Video4Linux)"))
57     set_category (CAT_PLAYLIST)
58     set_subcategory (SUBCAT_PLAYLIST_SD)
59     set_capability ("services_discovery", 0)
60     set_callbacks (OpenV4L, Close)
61     add_shortcut ("v4l", "video")
62 #ifdef HAVE_ALSA
63     add_submodule ()
64     set_shortname (N_("Audio capture"))
65     set_description (N_("Audio capture (ALSA)"))
66     set_category (CAT_PLAYLIST)
67     set_subcategory (SUBCAT_PLAYLIST_SD)
68     set_capability ("services_discovery", 0)
69     set_callbacks (OpenALSA, Close)
70     add_shortcut ("alsa", "audio")
71 #endif
72     add_submodule ()
73     set_shortname (N_("Discs"))
74     set_description (N_("Discs"))
75     set_category (CAT_PLAYLIST)
76     set_subcategory (SUBCAT_PLAYLIST_SD)
77     set_capability ("services_discovery", 0)
78     set_callbacks (OpenDisc, Close)
79     add_shortcut ("disc")
80
81     VLC_SD_PROBE_SUBMODULE
82
83 vlc_module_end ()
84
85 static int vlc_sd_probe_Open (vlc_object_t *obj)
86 {
87     vlc_probe_t *probe = (vlc_probe_t *)obj;
88
89     struct udev *udev = udev_new ();
90     if (udev == NULL)
91         return VLC_PROBE_CONTINUE;
92
93     struct udev_monitor *mon = udev_monitor_new_from_netlink (udev, "udev");
94     if (mon != NULL)
95     {
96         vlc_sd_probe_Add (probe, "v4l{longname=\"Video capture\"}",
97                           N_("Video capture"), SD_CAT_DEVICES);
98 #ifdef HAVE_ALSA
99         if (!module_exists ("pulselist"))
100             vlc_sd_probe_Add (probe, "alsa{longname=\"Audio capture\"}",
101                               N_("Audio capture"), SD_CAT_DEVICES);
102 #endif
103         vlc_sd_probe_Add (probe, "disc{longname=\"Discs\"}", N_("Discs"),
104                           SD_CAT_DEVICES);
105         udev_monitor_unref (mon);
106     }
107     udev_unref (udev);
108     return VLC_PROBE_CONTINUE;
109 }
110
111 struct device
112 {
113     dev_t devnum; /* must be first */
114     input_item_t *item;
115     services_discovery_t *sd;
116 };
117
118 struct subsys
119 {
120     const char *name;
121     char * (*get_mrl) (struct udev_device *dev);
122     char * (*get_name) (struct udev_device *dev);
123     int item_type;
124 };
125
126 struct services_discovery_sys_t
127 {
128     const struct subsys *subsys;
129     struct udev_monitor *monitor;
130     vlc_thread_t         thread;
131     void                *root;
132 };
133
134 /**
135  * Compares two devices (to support binary search).
136  */
137 static int cmpdev (const void *a, const void *b)
138 {
139     const dev_t *da = a, *db = b;
140     dev_t delta = *da - *db;
141
142     if (sizeof (delta) > sizeof (int))
143         return delta ? ((delta > 0) ? 1 : -1) : 0;
144     return (signed)delta;
145 }
146
147 static void DestroyDevice (void *data)
148 {
149     struct device *d = data;
150
151     if (d->sd)
152         services_discovery_RemoveItem (d->sd, d->item);
153     vlc_gc_decref (d->item);
154     free (d);
155 }
156
157 static char *decode_property (struct udev_device *, const char *);
158
159 /**
160  * Adds a udev device.
161  */
162 static int AddDevice (services_discovery_t *sd, struct udev_device *dev)
163 {
164     services_discovery_sys_t *p_sys = sd->p_sys;
165
166     char *mrl = p_sys->subsys->get_mrl (dev);
167     if (mrl == NULL)
168         return 0; /* don't know if it was an error... */
169     char *name = p_sys->subsys->get_name (dev);
170     input_item_t *item = input_item_NewWithType (mrl, name ? name : mrl,
171                                                  0, NULL, 0, -1,
172                                                  p_sys->subsys->item_type);
173     msg_Dbg (sd, "adding %s (%s)", mrl, name);
174     free (name);
175     free (mrl);
176     if (item == NULL)
177         return -1;
178
179     struct device *d = malloc (sizeof (*d));
180     if (d == NULL)
181     {
182         vlc_gc_decref (item);
183         return -1;
184     }
185     d->devnum = udev_device_get_devnum (dev);
186     d->item = item;
187     d->sd = NULL;
188
189     struct device **dp = tsearch (d, &p_sys->root, cmpdev);
190     if (dp == NULL) /* Out-of-memory */
191     {
192         DestroyDevice (d);
193         return -1;
194     }
195     if (*dp != d) /* Overwrite existing device */
196     {
197         DestroyDevice (*dp);
198         *dp = d;
199     }
200
201     services_discovery_AddItem (sd, item, NULL);
202     d->sd = sd;
203     return 0;
204 }
205
206 /**
207  * Removes a udev device (if present).
208  */
209 static void RemoveDevice (services_discovery_t *sd, struct udev_device *dev)
210 {
211     services_discovery_sys_t *p_sys = sd->p_sys;
212
213     dev_t num = udev_device_get_devnum (dev);
214     struct device **dp = tfind (&(dev_t){ num }, &p_sys->root, cmpdev);
215     if (dp == NULL)
216         return;
217
218     struct device *d = *dp;
219     tdelete (d, &p_sys->root, cmpdev);
220     DestroyDevice (d);
221 }
222
223 static void *Run (void *);
224
225 /**
226  * Probes and initializes.
227  */
228 static int Open (vlc_object_t *obj, const struct subsys *subsys)
229 {
230     services_discovery_t *sd = (services_discovery_t *)obj;
231     services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
232
233     if (p_sys == NULL)
234         return VLC_ENOMEM;
235     sd->p_sys = p_sys;
236     p_sys->subsys = subsys;
237     p_sys->root = NULL;
238
239     struct udev_monitor *mon = NULL;
240     struct udev *udev = udev_new ();
241     if (udev == NULL)
242         goto error;
243
244     mon = udev_monitor_new_from_netlink (udev, "udev");
245     if (mon == NULL
246      || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name,
247                                                          NULL))
248         goto error;
249     p_sys->monitor = mon;
250
251     /* Enumerate existing devices */
252     struct udev_enumerate *devenum = udev_enumerate_new (udev);
253     if (devenum == NULL)
254         goto error;
255     if (udev_enumerate_add_match_subsystem (devenum, subsys->name))
256     {
257         udev_enumerate_unref (devenum);
258         goto error;
259     }
260
261     udev_monitor_enable_receiving (mon);
262     /* Note that we enumerate _after_ monitoring is enabled so that we do not
263      * loose device events occuring while we are enumerating. We could still
264      * loose events if the Netlink socket receive buffer overflows. */
265     udev_enumerate_scan_devices (devenum);
266     struct udev_list_entry *devlist = udev_enumerate_get_list_entry (devenum);
267     struct udev_list_entry *deventry;
268     udev_list_entry_foreach (deventry, devlist)
269     {
270         const char *path = udev_list_entry_get_name (deventry);
271         struct udev_device *dev = udev_device_new_from_syspath (udev, path);
272         AddDevice (sd, dev);
273         udev_device_unref (dev);
274     }
275     udev_enumerate_unref (devenum);
276
277     if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
278     {   /* Fallback without thread */
279         udev_monitor_unref (mon);
280         udev_unref (udev);
281         p_sys->monitor = NULL;
282     }
283     return VLC_SUCCESS;
284
285 error:
286     if (mon != NULL)
287         udev_monitor_unref (mon);
288     if (udev != NULL)
289         udev_unref (udev);
290     free (p_sys);
291     return VLC_EGENERIC;
292 }
293
294 /**
295  * Releases resources
296  */
297 static void Close (vlc_object_t *obj)
298 {
299     services_discovery_t *sd = (services_discovery_t *)obj;
300     services_discovery_sys_t *p_sys = sd->p_sys;
301
302     if (p_sys->monitor != NULL)
303     {
304         struct udev *udev = udev_monitor_get_udev (p_sys->monitor);
305
306         vlc_cancel (p_sys->thread);
307         vlc_join (p_sys->thread, NULL);
308         udev_monitor_unref (p_sys->monitor);
309         udev_unref (udev);
310     }
311
312     tdestroy (p_sys->root, DestroyDevice);
313     free (p_sys);
314 }
315
316 static void *Run (void *data)
317 {
318     services_discovery_t *sd = data;
319     services_discovery_sys_t *p_sys = sd->p_sys;
320     struct udev_monitor *mon = p_sys->monitor;
321
322     int fd = udev_monitor_get_fd (mon);
323     struct pollfd ufd = { .fd = fd, .events = POLLIN, };
324
325     for (;;)
326     {
327         while (poll (&ufd, 1, -1) == -1)
328             if (errno != EINTR)
329                 break;
330
331         int canc = vlc_savecancel ();
332         struct udev_device *dev = udev_monitor_receive_device (mon);
333         if (dev == NULL)
334             continue;
335
336         const char *action = udev_device_get_action (dev);
337         if (!strcmp (action, "add"))
338             AddDevice (sd, dev);
339         else if (!strcmp (action, "remove"))
340             RemoveDevice (sd, dev);
341         else if (!strcmp (action, "change"))
342         {
343             RemoveDevice (sd, dev);
344             AddDevice (sd, dev);
345         }
346         udev_device_unref (dev);
347         vlc_restorecancel (canc);
348     }
349     return NULL;
350 }
351
352 /**
353  * Converts an hexadecimal digit to an integer.
354  */
355 static int hex (char c)
356 {
357     if (c >= '0' && c <= '9')
358         return c - '0';
359     if (c >= 'A' && c <= 'F')
360         return c + 10 - 'A';
361     if (c >= 'a' && c <= 'f')
362         return c + 10 - 'a';
363     return -1;
364 }
365
366 /**
367  * Decodes a udev hexadecimal-encoded property.
368  */
369 static char *decode (const char *enc)
370 {
371     char *ret = enc ? strdup (enc) : NULL;
372     if (ret == NULL)
373         return NULL;
374
375     char *out = ret;
376     for (const char *in = ret; *in; out++)
377     {
378         int h1, h2;
379
380         if ((in[0] == '\\') && (in[1] == 'x')
381          && ((h1 = hex (in[2])) != -1)
382          && ((h2 = hex (in[3])) != -1))
383         {
384             *out = (h1 << 4) | h2;
385             in += 4;
386         }
387         else
388         {
389             *out = *in;
390             in++;
391         }
392     }
393     *out = 0;
394     return ret;
395 }
396
397 static char *decode_property (struct udev_device *dev, const char *name)
398 {
399     return decode (udev_device_get_property_value (dev, name));
400 }
401
402
403 /*** Video4Linux support ***/
404 static bool v4l_is_legacy (struct udev_device *dev)
405 {
406     const char *version;
407
408     version = udev_device_get_property_value (dev, "ID_V4L_VERSION");
409     return (version != NULL) && !strcmp (version, "1");
410 }
411
412 static bool v4l_can_capture (struct udev_device *dev)
413 {
414     const char *caps;
415
416     caps = udev_device_get_property_value (dev, "ID_V4L_CAPABILITIES");
417     return (caps != NULL) && (strstr (caps, ":capture:") != NULL);
418 }
419
420 static char *v4l_get_mrl (struct udev_device *dev)
421 {
422     /* Determine media location */
423     if (v4l_is_legacy (dev) || !v4l_can_capture (dev))
424         return NULL;
425
426     const char *node = udev_device_get_devnode (dev);
427     char *mrl;
428
429     if (asprintf (&mrl, "v4l2://%s", node) == -1)
430         mrl = NULL;
431     return mrl;
432 }
433
434 static char *v4l_get_name (struct udev_device *dev)
435 {
436     const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT");
437     return prd ? strdup (prd) : NULL;
438 }
439
440 int OpenV4L (vlc_object_t *obj)
441 {
442     static const struct subsys subsys = {
443         "video4linux", v4l_get_mrl, v4l_get_name, ITEM_TYPE_CARD,
444     };
445
446     return Open (obj, &subsys);
447 }
448
449
450 #ifdef HAVE_ALSA
451 /*** Advanced Linux Sound Architecture support ***/
452 #include <alsa/asoundlib.h>
453
454 static int alsa_get_device (struct udev_device *dev, unsigned *restrict pcard,
455                             unsigned *restrict pdevice)
456 {
457     const char *node = udev_device_get_devpath (dev);
458     char type;
459
460     node = strrchr (node, '/');
461     if (node == NULL)
462         return -1;
463     if (sscanf (node, "/pcmC%uD%u%c", pcard, pdevice, &type) < 3)
464         return -1;
465     if (type != 'c')
466         return -1;
467     return 0;
468 }
469
470
471 static char *alsa_get_mrl (struct udev_device *dev)
472 {
473     /* Determine media location */
474     char *mrl;
475     unsigned card, device;
476
477     if (alsa_get_device (dev, &card, &device))
478         return NULL;
479
480     if (asprintf (&mrl, "alsa://plughw:%u,%u", card, device) == -1)
481         mrl = NULL;
482     return mrl;
483 }
484
485 static char *alsa_get_name (struct udev_device *dev)
486 {
487     char *name = NULL;
488     unsigned card, device;
489
490     if (alsa_get_device (dev, &card, &device))
491         return NULL;
492
493     char card_name[4 + 3 * sizeof (int)];
494     snprintf (card_name, sizeof (card_name), "hw:%u", card);
495
496     snd_ctl_t *ctl;
497     if (snd_ctl_open (&ctl, card_name, 0))
498         return NULL;
499
500     snd_pcm_info_t *pcm_info;
501     snd_pcm_info_alloca (&pcm_info);
502     snd_pcm_info_set_device (pcm_info, device);
503     snd_pcm_info_set_subdevice (pcm_info, 0);
504     snd_pcm_info_set_stream (pcm_info, SND_PCM_STREAM_CAPTURE);
505     if (snd_ctl_pcm_info (ctl, pcm_info))
506         goto out;
507
508     name = strdup (snd_pcm_info_get_name (pcm_info));
509 out:
510     snd_ctl_close (ctl);
511     return name;
512 }
513
514 int OpenALSA (vlc_object_t *obj)
515 {
516     static const struct subsys subsys = {
517         "sound", alsa_get_mrl, alsa_get_name, ITEM_TYPE_CARD,
518     };
519
520     return Open (obj, &subsys);
521 }
522 #endif /* HAVE_ALSA */
523
524
525 /*** Discs support ***/
526 static char *disc_get_mrl (struct udev_device *dev)
527 {
528     const char *node = udev_device_get_devnode (dev);
529     const char *val;
530
531     val = udev_device_get_property_value (dev, "ID_CDROM");
532     if (val == NULL)
533         return NULL; /* Ignore non-optical block devices */
534
535     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_STATE");
536     if (val == NULL)
537     {   /* Force probing of the disc in the drive if any. */
538         int fd = open (node, O_RDONLY);
539         close (fd);
540         return NULL;
541     }
542     if (!strcmp (val, "blank"))
543         return NULL; /* ignore empty drives and virgin recordable discs */
544
545     const char *scheme = NULL;
546     val = udev_device_get_property_value (dev,
547                                           "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
548     if (val && atoi (val))
549         scheme = "cdda"; /* Audio CD rather than file system */
550     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
551     if (val && atoi (val))
552         scheme = "dvd";
553
554     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
555     if (val && atoi (val))
556         scheme = "bluray";
557 #ifdef LOL
558     val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
559     if (val && atoi (val))
560         scheme = "hddvd";
561 #endif
562
563     /* We didn't get any property that could tell we have optical disc
564        that we can play */
565     if (scheme == NULL)
566         return NULL;
567
568     return vlc_path2uri (node, scheme);
569 }
570
571 static char *disc_get_name (struct udev_device *dev)
572 {
573     char *name = NULL;
574     struct udev_list_entry *list, *entry;
575
576     list = udev_device_get_properties_list_entry (dev);
577     if (unlikely(list == NULL))
578         return NULL;
579
580     const char *cat = NULL;
581     udev_list_entry_foreach (entry, list)
582     {
583         const char *name = udev_list_entry_get_name (entry);
584
585         if (strncmp (name, "ID_CDROM_MEDIA_", 15))
586             continue;
587         if (!atoi (udev_list_entry_get_value (entry)))
588             continue;
589         name += 15;
590
591         if (!strncmp (name, "CD", 2))
592             cat = N_("CD");
593         else if (!strncmp (name, "DVD", 3))
594             cat = N_("DVD");
595         else if (!strncmp (name, "BD", 2))
596             cat = N_("Blu-ray");
597         else if (!strncmp (name, "HDDVD", 5))
598             cat = N_("HD DVD");
599
600         if (cat != NULL)
601             break;
602     }
603
604     if (cat == NULL)
605         cat = N_("Unknown type");
606
607     char *label = decode_property (dev, "ID_FS_LABEL_ENC");
608
609     if (label)
610         if (asprintf(&name, "%s (%s)", label, vlc_gettext(cat)) < 0)
611             name = NULL;
612     free(label);
613
614     return name;
615 }
616
617 int OpenDisc (vlc_object_t *obj)
618 {
619     static const struct subsys subsys = {
620         "block", disc_get_mrl, disc_get_name, ITEM_TYPE_DISC,
621     };
622
623     return Open (obj, &subsys);
624 }