1/***
2  This file is part of PulseAudio.
3
4  Copyright 2009 Lennart Poettering
5
6  PulseAudio is free software; you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as
8  published by the Free Software Foundation; either version 2.1 of the
9  License, or (at your option) any later version.
10
11  PulseAudio is distributed in the hope that it will be useful, but
12  WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  General Public License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18***/
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include <libudev.h>
25
26#include <pulse/xmalloc.h>
27#include <pulse/proplist.h>
28
29#include <pulsecore/log.h>
30#include <pulsecore/core-util.h>
31
32#include "udev-util.h"
33
34static int read_id(struct udev_device *d, const char *n) {
35    const char *v;
36    unsigned u;
37
38    pa_assert(d);
39    pa_assert(n);
40
41    if (!(v = udev_device_get_property_value(d, n)))
42        return -1;
43
44    if (pa_startswith(v, "0x"))
45        v += 2;
46
47    if (!*v)
48        return -1;
49
50    if (sscanf(v, "%04x", &u) != 1)
51        return -1;
52
53    if (u > 0xFFFFU)
54        return -1;
55
56    return u;
57}
58
59static int dehex(char x) {
60    if (x >= '0' && x <= '9')
61        return x - '0';
62
63    if (x >= 'A' && x <= 'F')
64        return x - 'A' + 10;
65
66    if (x >= 'a' && x <= 'f')
67        return x - 'a' + 10;
68
69    return -1;
70}
71
72static void proplist_sets_unescape(pa_proplist *p, const char *prop, const char *s) {
73    const char *f;
74    char *t, *r;
75    int c = 0;
76
77    enum {
78        TEXT,
79        BACKSLASH,
80        EX,
81        FIRST
82    } state = TEXT;
83
84    /* The resulting string is definitely shorter than the source string */
85    r = pa_xnew(char, strlen(s)+1);
86
87    for (f = s, t = r; *f; f++) {
88
89        switch (state) {
90
91            case TEXT:
92                if (*f == '\\')
93                    state = BACKSLASH;
94                else
95                    *(t++) = *f;
96                break;
97
98            case BACKSLASH:
99                if (*f == 'x')
100                    state = EX;
101                else {
102                    *(t++) = '\\';
103                    *(t++) = *f;
104                    state = TEXT;
105                }
106                break;
107
108            case EX:
109                c = dehex(*f);
110
111                if (c < 0) {
112                    *(t++) = '\\';
113                    *(t++) = 'x';
114                    *(t++) = *f;
115                    state = TEXT;
116                } else
117                    state = FIRST;
118
119                break;
120
121            case FIRST: {
122                int d = dehex(*f);
123
124                if (d < 0) {
125                    *(t++) = '\\';
126                    *(t++) = 'x';
127                    *(t++) = *(f-1);
128                    *(t++) = *f;
129                } else
130                    *(t++) = (char) (c << 4) | d;
131
132                state = TEXT;
133                break;
134            }
135        }
136    }
137
138    switch (state) {
139
140        case TEXT:
141            break;
142
143        case BACKSLASH:
144            *(t++) = '\\';
145            break;
146
147        case EX:
148            *(t++) = '\\';
149            *(t++) = 'x';
150            break;
151
152        case FIRST:
153            *(t++) = '\\';
154            *(t++) = 'x';
155            *(t++) = *(f-1);
156            break;
157    }
158
159    *t = 0;
160
161    pa_proplist_sets(p, prop, r);
162    pa_xfree(r);
163}
164
165int pa_udev_get_info(int card_idx, pa_proplist *p) {
166    int r = -1;
167    struct udev *udev;
168    struct udev_device *card = NULL;
169    char *t;
170    const char *v;
171    int id;
172
173    pa_assert(p);
174    pa_assert(card_idx >= 0);
175
176    if (!(udev = udev_new())) {
177        pa_log_error("Failed to allocate udev context.");
178        goto finish;
179    }
180
181    t = pa_sprintf_malloc("/sys/class/sound/card%i", card_idx);
182    card = udev_device_new_from_syspath(udev, t);
183    pa_xfree(t);
184
185    if (!card) {
186        pa_log_error("Failed to get card object.");
187        goto finish;
188    }
189
190    if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS_PATH))
191        if (((v = udev_device_get_property_value(card, "ID_PATH")) && *v) ||
192            (v = udev_device_get_devpath(card)))
193            pa_proplist_sets(p, PA_PROP_DEVICE_BUS_PATH, v);
194
195    if (!pa_proplist_contains(p, "sysfs.path"))
196        if ((v = udev_device_get_devpath(card)))
197            pa_proplist_sets(p, "sysfs.path", v);
198
199    if (!pa_proplist_contains(p, "udev.id"))
200        if ((v = udev_device_get_property_value(card, "ID_ID")) && *v)
201            pa_proplist_sets(p, "udev.id", v);
202
203    if (!pa_proplist_contains(p, PA_PROP_DEVICE_BUS))
204        if ((v = udev_device_get_property_value(card, "ID_BUS")) && *v)
205            pa_proplist_sets(p, PA_PROP_DEVICE_BUS, v);
206
207    if (!pa_proplist_contains(p, PA_PROP_DEVICE_VENDOR_ID))
208        if ((id = read_id(card, "ID_VENDOR_ID")) > 0)
209            pa_proplist_setf(p, PA_PROP_DEVICE_VENDOR_ID, "%04x", id);
210
211    if (!pa_proplist_contains(p, PA_PROP_DEVICE_VENDOR_NAME)) {
212        if ((v = udev_device_get_property_value(card, "ID_VENDOR_FROM_DATABASE")) && *v)
213            pa_proplist_sets(p, PA_PROP_DEVICE_VENDOR_NAME, v);
214        else if ((v = udev_device_get_property_value(card, "ID_VENDOR_ENC")) && *v)
215            proplist_sets_unescape(p, PA_PROP_DEVICE_VENDOR_NAME, v);
216        else if ((v = udev_device_get_property_value(card, "ID_VENDOR")) && *v)
217            pa_proplist_sets(p, PA_PROP_DEVICE_VENDOR_NAME, v);
218    }
219
220    if (!pa_proplist_contains(p, PA_PROP_DEVICE_PRODUCT_ID))
221        if ((id = read_id(card, "ID_MODEL_ID")) >= 0)
222            pa_proplist_setf(p, PA_PROP_DEVICE_PRODUCT_ID, "%04x", id);
223
224    if (!pa_proplist_contains(p, PA_PROP_DEVICE_PRODUCT_NAME)) {
225        if ((v = udev_device_get_property_value(card, "ID_MODEL_FROM_DATABASE")) && *v)
226            pa_proplist_sets(p, PA_PROP_DEVICE_PRODUCT_NAME, v);
227        else if ((v = udev_device_get_property_value(card, "ID_MODEL_ENC")) && *v)
228            proplist_sets_unescape(p, PA_PROP_DEVICE_PRODUCT_NAME, v);
229        else if ((v = udev_device_get_property_value(card, "ID_MODEL")) && *v)
230            pa_proplist_sets(p, PA_PROP_DEVICE_PRODUCT_NAME, v);
231    }
232
233    if (!pa_proplist_contains(p, PA_PROP_DEVICE_SERIAL))
234        if ((v = udev_device_get_property_value(card, "ID_SERIAL")) && *v)
235            pa_proplist_sets(p, PA_PROP_DEVICE_SERIAL, v);
236
237    if (!pa_proplist_contains(p, PA_PROP_DEVICE_CLASS))
238        if ((v = udev_device_get_property_value(card, "SOUND_CLASS")) && *v)
239            pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, v);
240
241    if (!pa_proplist_contains(p, PA_PROP_DEVICE_FORM_FACTOR))
242        if ((v = udev_device_get_property_value(card, "SOUND_FORM_FACTOR")) && *v)
243            pa_proplist_sets(p, PA_PROP_DEVICE_FORM_FACTOR, v);
244
245    /* This is normally not set by the udev rules but may be useful to
246     * allow administrators to overwrite the device description.*/
247    if (!pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION))
248        if ((v = udev_device_get_property_value(card, "SOUND_DESCRIPTION")) && *v)
249            pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, v);
250
251    r = 0;
252
253finish:
254
255    if (card)
256        udev_device_unref(card);
257
258    if (udev)
259        udev_unref(udev);
260
261    return r;
262}
263
264char* pa_udev_get_property(int card_idx, const char *name) {
265    struct udev *udev;
266    struct udev_device *card = NULL;
267    char *t, *r = NULL;
268    const char *v;
269
270    pa_assert(card_idx >= 0);
271    pa_assert(name);
272
273    if (!(udev = udev_new())) {
274        pa_log_error("Failed to allocate udev context.");
275        goto finish;
276    }
277
278    t = pa_sprintf_malloc("/sys/class/sound/card%i", card_idx);
279    card = udev_device_new_from_syspath(udev, t);
280    pa_xfree(t);
281
282    if (!card) {
283        pa_log_error("Failed to get card object.");
284        goto finish;
285    }
286
287    if ((v = udev_device_get_property_value(card, name)) && *v)
288        r = pa_xstrdup(v);
289
290finish:
291
292    if (card)
293        udev_device_unref(card);
294
295    if (udev)
296        udev_unref(udev);
297
298    return r;
299}
300