Add hotplug support to the Linux backend.
[platform/upstream/libusb.git] / libusb / os / linux_netlink.c
1 /* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */
2 /*
3  * Linux usbfs backend for libusb
4  * Copyright (C) 2007-2009 Daniel Drake <dsd@gentoo.org>
5  * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com>
6  * Copyright (c) 2013 Nathan Hjelm <hjelmn@mac.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library 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 GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22
23 #include "config.h"
24 #include <ctype.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <poll.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <arpa/inet.h>
35
36 #include "libusb.h"
37 #include "libusbi.h"
38 #include "linux_usbfs.h"
39
40 #include <linux/netlink.h>
41 #include <linux/filter.h>
42
43 #define KERNEL 1
44
45 static int linux_netlink_socket = -1;
46 static pthread_t libusb_linux_event_thread;
47
48 static void *linux_netlink_event_thread_main(void *arg);
49
50 struct sockaddr_nl snl = { .nl_family=AF_NETLINK, .nl_groups=KERNEL };
51
52 int linux_netlink_start_event_monitor(void)
53 {
54         int ret;
55
56         snl.nl_groups = KERNEL;
57
58         linux_netlink_socket = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT);
59         if (-1 == linux_netlink_socket) {
60                 return LIBUSB_ERROR_OTHER;
61         }
62
63         ret = bind(linux_netlink_socket, (struct sockaddr *) &snl, sizeof(snl));
64         if (0 != ret) {
65                 return LIBUSB_ERROR_OTHER;
66         }
67
68         /* TODO -- add authentication */
69         /* setsockopt(linux_netlink_socket, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); */
70
71         ret = pthread_create(&libusb_linux_event_thread, NULL, linux_netlink_event_thread_main, NULL);
72         if (0 != ret) {
73                 return LIBUSB_ERROR_OTHER;
74         }
75
76         return LIBUSB_SUCCESS;
77 }
78
79 int linux_netlink_stop_event_monitor(void)
80 {
81         int r;
82
83         if (-1 == linux_netlink_socket) {
84                 /* already closed. nothing to do */
85                 return LIBUSB_SUCCESS;
86         }
87
88         r = close(linux_netlink_socket);
89         if (0 > r) {
90                 usbi_err(NULL, "error closing netlink socket. %s", strerror(errno));
91                 return LIBUSB_ERROR_OTHER;
92         }
93
94         pthread_cancel(libusb_linux_event_thread);
95
96         linux_netlink_socket = -1;
97
98         return LIBUSB_SUCCESS;
99 }
100
101 static const char *netlink_message_parse (const char *buffer, size_t len, const char *key)
102 {
103         size_t keylen = strlen(key);
104         size_t offset;
105
106         for (offset = 0 ; offset < len && '\0' != buffer[offset] ; offset += strlen(buffer + offset) + 1) {
107                 if (0 == strncmp(buffer + offset, key, keylen) &&
108                     '=' == buffer[offset + keylen]) {
109                         return buffer + offset + keylen + 1;
110                 }
111         }
112
113         return NULL;
114 }
115
116 /* parse parts of netlink message common to both libudev and the kernel */
117 static int linux_netlink_parse(char *buffer, size_t len, int *detached, const char **sys_name,
118                                uint8_t *busnum, uint8_t *devaddr) {
119         const char *tmp;
120         int i;
121
122         errno = 0;
123
124         *sys_name = NULL;
125         *detached = 0;
126         *busnum   = 0;
127         *devaddr  = 0;
128
129         tmp = netlink_message_parse((const char *) buffer, len, "ACTION");
130         if (0 == strcmp(tmp, "remove")) {
131                 *detached = 1;
132         } else if (0 != strcmp(tmp, "add")) {
133                 usbi_dbg("unknown device action");
134                 return -1;
135         }
136
137         /* check that this is a usb message */
138         tmp = netlink_message_parse(buffer, len, "SUBSYSTEM");
139         if (NULL == tmp || 0 != strcmp(tmp, "usb")) {
140                 /* not usb. ignore */
141                 return -1;
142         }
143
144         tmp = netlink_message_parse(buffer, len, "BUSNUM");
145         if (NULL == tmp) {
146                 /* no bus number (likely a usb interface). ignore*/
147                 return -1;
148         }
149
150         *busnum = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff);
151         if (errno) {
152                 errno = 0;
153                 return -1;
154         }
155
156         tmp = netlink_message_parse(buffer, len, "DEVNUM");
157         if (NULL == tmp) {
158                 return -1;
159         }
160
161         *devaddr = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff);
162         if (errno) {
163                 errno = 0;
164                 return -1;
165         }
166
167         tmp = netlink_message_parse(buffer, len, "DEVPATH");
168         if (NULL == tmp) {
169                 return -1;
170         }
171
172         for (i = strlen(tmp) - 1 ; i ; --i) {
173                 if ('/' ==tmp[i]) {
174                         *sys_name = tmp + i + 1;
175                         break;
176                 }
177         }
178
179         /* found a usb device */
180         return 0;
181 }
182
183 static void *linux_netlink_event_thread_main(void *arg)
184 {
185         struct pollfd fds = {.fd = linux_netlink_socket,
186                              .events = POLLIN};
187         unsigned char buffer[1024];
188         struct iovec iov = {.iov_base = buffer, .iov_len = sizeof(buffer)};
189         struct msghdr meh = { .msg_iov=&iov, .msg_iovlen=1,
190                              .msg_name=&snl, .msg_namelen=sizeof(snl) };
191         uint8_t busnum, devaddr;
192         int detached, r;
193         size_t len;
194
195         /* silence compiler warning */
196         (void) arg;
197
198         while (1 == poll(&fds, 1, -1)) {
199                 const char *sys_name = NULL;
200
201                 if (POLLIN != fds.revents) {
202                         break;
203                 }
204
205                 /* read netlink message */
206                 memset(buffer, 0, sizeof(buffer));
207                 len = recvmsg(linux_netlink_socket, &meh, 0);
208                 if (len < 32) {
209                         usbi_dbg("error recieving message from netlink");
210                         continue;
211                 }
212
213                 /* TODO -- authenticate this message is from the kernel or udevd */
214
215                 r = linux_netlink_parse(buffer, len, &detached, &sys_name,
216                                         &busnum, &devaddr);
217                 if (r)
218                         continue;
219
220                 usbi_dbg("netlink hotplug found device busnum: %hhu, devaddr: %hhu, sys_name: %s, removed: %s",
221                          busnum, devaddr, sys_name, detached ? "yes" : "no");
222
223                 /* signal device is available (or not) to all contexts */
224                 if (detached)
225                         linux_hotplug_disconnected(busnum, devaddr, sys_name);
226                 else
227                         linux_hotplug_enumerate(busnum, devaddr, sys_name);
228         }
229
230         return NULL;
231 }