• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  * Copyright (c) 2016 Chris Dickens <christopher.a.dickens@gmail.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  */
23 
24 #include "libusbi.h"
25 #include "linux_usbfs.h"
26 
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <poll.h>
30 #include <pthread.h>
31 #include <string.h>
32 #include <unistd.h>
33 
34 #ifdef HAVE_ASM_TYPES_H
35 #include <asm/types.h>
36 #endif
37 #include <linux/netlink.h>
38 #include <sys/socket.h>
39 
40 #define NL_GROUP_KERNEL 1
41 
42 #ifndef SOCK_CLOEXEC
43 #define SOCK_CLOEXEC	0
44 #endif
45 
46 #ifndef SOCK_NONBLOCK
47 #define SOCK_NONBLOCK	0
48 #endif
49 
50 static int linux_netlink_socket = -1;
51 static usbi_event_t netlink_control_event = USBI_INVALID_EVENT;
52 static pthread_t libusb_linux_event_thread;
53 
54 static void *linux_netlink_event_thread_main(void *arg);
55 
set_fd_cloexec_nb(int fd,int socktype)56 static int set_fd_cloexec_nb(int fd, int socktype)
57 {
58 	int flags;
59 
60 #if defined(FD_CLOEXEC)
61 	/* Make sure the netlink socket file descriptor is marked as CLOEXEC */
62 	if (!(socktype & SOCK_CLOEXEC)) {
63 		flags = fcntl(fd, F_GETFD);
64 		if (flags == -1) {
65 			usbi_err(NULL, "failed to get netlink fd flags, errno=%d", errno);
66 			return -1;
67 		}
68 
69 		if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
70 			usbi_err(NULL, "failed to set netlink fd flags, errno=%d", errno);
71 			return -1;
72 		}
73 	}
74 #endif
75 
76 	/* Make sure the netlink socket is non-blocking */
77 	if (!(socktype & SOCK_NONBLOCK)) {
78 		flags = fcntl(fd, F_GETFL);
79 		if (flags == -1) {
80 			usbi_err(NULL, "failed to get netlink fd status flags, errno=%d", errno);
81 			return -1;
82 		}
83 
84 		if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
85 			usbi_err(NULL, "failed to set netlink fd status flags, errno=%d", errno);
86 			return -1;
87 		}
88 	}
89 
90 	return 0;
91 }
92 
linux_netlink_start_event_monitor(void)93 int linux_netlink_start_event_monitor(void)
94 {
95 	struct sockaddr_nl sa_nl = { .nl_family = AF_NETLINK, .nl_groups = NL_GROUP_KERNEL };
96 	int socktype = SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC;
97 	int opt = 1;
98 	int ret;
99 
100 	linux_netlink_socket = socket(PF_NETLINK, socktype, NETLINK_KOBJECT_UEVENT);
101 	if (linux_netlink_socket == -1 && errno == EINVAL) {
102 		usbi_dbg("failed to create netlink socket of type %d, attempting SOCK_RAW", socktype);
103 		socktype = SOCK_RAW;
104 		linux_netlink_socket = socket(PF_NETLINK, socktype, NETLINK_KOBJECT_UEVENT);
105 	}
106 
107 	if (linux_netlink_socket == -1) {
108 		usbi_err(NULL, "failed to create netlink socket, errno=%d", errno);
109 		goto err;
110 	}
111 
112 	ret = set_fd_cloexec_nb(linux_netlink_socket, socktype);
113 	if (ret == -1)
114 		goto err_close_socket;
115 
116 	ret = bind(linux_netlink_socket, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
117 	if (ret == -1) {
118 		usbi_err(NULL, "failed to bind netlink socket, errno=%d", errno);
119 		goto err_close_socket;
120 	}
121 
122 	ret = setsockopt(linux_netlink_socket, SOL_SOCKET, SO_PASSCRED, &opt, sizeof(opt));
123 	if (ret == -1) {
124 		usbi_err(NULL, "failed to set netlink socket SO_PASSCRED option, errno=%d", errno);
125 		goto err_close_socket;
126 	}
127 
128 	ret = usbi_create_event(&netlink_control_event);
129 	if (ret) {
130 		usbi_err(NULL, "failed to create netlink control event");
131 		goto err_close_socket;
132 	}
133 
134 	ret = pthread_create(&libusb_linux_event_thread, NULL, linux_netlink_event_thread_main, NULL);
135 	if (ret != 0) {
136 		usbi_err(NULL, "failed to create netlink event thread (%d)", ret);
137 		goto err_destroy_event;
138 	}
139 
140 	return LIBUSB_SUCCESS;
141 
142 err_destroy_event:
143 	usbi_destroy_event(&netlink_control_event);
144 	netlink_control_event = (usbi_event_t)USBI_INVALID_EVENT;
145 err_close_socket:
146 	close(linux_netlink_socket);
147 	linux_netlink_socket = -1;
148 err:
149 	return LIBUSB_ERROR_OTHER;
150 }
151 
linux_netlink_stop_event_monitor(void)152 int linux_netlink_stop_event_monitor(void)
153 {
154 	int ret;
155 
156 	assert(linux_netlink_socket != -1);
157 
158 	/* Signal the control event and wait for the thread to exit */
159 	usbi_signal_event(&netlink_control_event);
160 
161 	ret = pthread_join(libusb_linux_event_thread, NULL);
162 	if (ret)
163 		usbi_warn(NULL, "failed to join netlink event thread (%d)", ret);
164 
165 	usbi_destroy_event(&netlink_control_event);
166 	netlink_control_event = (usbi_event_t)USBI_INVALID_EVENT;
167 
168 	close(linux_netlink_socket);
169 	linux_netlink_socket = -1;
170 
171 	return LIBUSB_SUCCESS;
172 }
173 
netlink_message_parse(const char * buffer,size_t len,const char * key)174 static const char *netlink_message_parse(const char *buffer, size_t len, const char *key)
175 {
176 	const char *end = buffer + len;
177 	size_t keylen = strlen(key);
178 
179 	while (buffer < end && *buffer) {
180 		if (strncmp(buffer, key, keylen) == 0 && buffer[keylen] == '=')
181 			return buffer + keylen + 1;
182 		buffer += strlen(buffer) + 1;
183 	}
184 
185 	return NULL;
186 }
187 
188 /* parse parts of netlink message common to both libudev and the kernel */
linux_netlink_parse(const char * buffer,size_t len,int * detached,const char ** sys_name,uint8_t * busnum,uint8_t * devaddr)189 static int linux_netlink_parse(const char *buffer, size_t len, int *detached,
190 	const char **sys_name, uint8_t *busnum, uint8_t *devaddr)
191 {
192 	const char *tmp, *slash;
193 
194 	errno = 0;
195 
196 	*sys_name = NULL;
197 	*detached = 0;
198 	*busnum   = 0;
199 	*devaddr  = 0;
200 
201 	tmp = netlink_message_parse(buffer, len, "ACTION");
202 	if (!tmp) {
203 		return -1;
204 	} else if (strcmp(tmp, "remove") == 0) {
205 		*detached = 1;
206 	} else if (strcmp(tmp, "add") != 0) {
207 		usbi_dbg("unknown device action %s", tmp);
208 		return -1;
209 	}
210 
211 	/* check that this is a usb message */
212 	tmp = netlink_message_parse(buffer, len, "SUBSYSTEM");
213 	if (!tmp || strcmp(tmp, "usb") != 0) {
214 		/* not usb. ignore */
215 		return -1;
216 	}
217 
218 	/* check that this is an actual usb device */
219 	tmp = netlink_message_parse(buffer, len, "DEVTYPE");
220 	if (!tmp || strcmp(tmp, "usb_device") != 0) {
221 		/* not usb. ignore */
222 		return -1;
223 	}
224 
225 	tmp = netlink_message_parse(buffer, len, "BUSNUM");
226 	if (tmp) {
227 		*busnum = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff);
228 		if (errno) {
229 			errno = 0;
230 			return -1;
231 		}
232 
233 		tmp = netlink_message_parse(buffer, len, "DEVNUM");
234 		if (NULL == tmp)
235 			return -1;
236 
237 		*devaddr = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff);
238 		if (errno) {
239 			errno = 0;
240 			return -1;
241 		}
242 	} else {
243 		/* no bus number. try "DEVICE" */
244 		tmp = netlink_message_parse(buffer, len, "DEVICE");
245 		if (!tmp) {
246 			/* not usb. ignore */
247 			return -1;
248 		}
249 
250 		/* Parse a device path such as /dev/bus/usb/003/004 */
251 		slash = strrchr(tmp, '/');
252 		if (!slash)
253 			return -1;
254 
255 		*busnum = (uint8_t)(strtoul(slash - 3, NULL, 10) & 0xff);
256 		if (errno) {
257 			errno = 0;
258 			return -1;
259 		}
260 
261 		*devaddr = (uint8_t)(strtoul(slash + 1, NULL, 10) & 0xff);
262 		if (errno) {
263 			errno = 0;
264 			return -1;
265 		}
266 
267 		return 0;
268 	}
269 
270 	tmp = netlink_message_parse(buffer, len, "DEVPATH");
271 	if (!tmp)
272 		return -1;
273 
274 	slash = strrchr(tmp, '/');
275 	if (slash)
276 		*sys_name = slash + 1;
277 
278 	/* found a usb device */
279 	return 0;
280 }
281 
linux_netlink_read_message(void)282 static int linux_netlink_read_message(void)
283 {
284 	char cred_buffer[CMSG_SPACE(sizeof(struct ucred))];
285 	char msg_buffer[2048];
286 	const char *sys_name = NULL;
287 	uint8_t busnum, devaddr;
288 	int detached, r;
289 	ssize_t len;
290 	struct cmsghdr *cmsg;
291 	struct ucred *cred;
292 	struct sockaddr_nl sa_nl;
293 	struct iovec iov = { .iov_base = msg_buffer, .iov_len = sizeof(msg_buffer) };
294 	struct msghdr msg = {
295 		.msg_iov = &iov, .msg_iovlen = 1,
296 		.msg_control = cred_buffer, .msg_controllen = sizeof(cred_buffer),
297 		.msg_name = &sa_nl, .msg_namelen = sizeof(sa_nl)
298 	};
299 
300 	/* read netlink message */
301 	len = recvmsg(linux_netlink_socket, &msg, 0);
302 	if (len == -1) {
303 		if (errno != EAGAIN && errno != EINTR)
304 			usbi_err(NULL, "error receiving message from netlink, errno=%d", errno);
305 		return -1;
306 	}
307 
308 	if (len < 32 || (msg.msg_flags & MSG_TRUNC)) {
309 		usbi_err(NULL, "invalid netlink message length");
310 		return -1;
311 	}
312 
313 	if (sa_nl.nl_groups != NL_GROUP_KERNEL || sa_nl.nl_pid != 0) {
314 		usbi_dbg("ignoring netlink message from unknown group/PID (%u/%u)",
315 			 (unsigned int)sa_nl.nl_groups, (unsigned int)sa_nl.nl_pid);
316 		return -1;
317 	}
318 
319 	cmsg = CMSG_FIRSTHDR(&msg);
320 	if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) {
321 		usbi_dbg("ignoring netlink message with no sender credentials");
322 		return -1;
323 	}
324 
325 	cred = (struct ucred *)CMSG_DATA(cmsg);
326 	if (cred->uid != 0) {
327 		usbi_dbg("ignoring netlink message with non-zero sender UID %u", (unsigned int)cred->uid);
328 		return -1;
329 	}
330 
331 	r = linux_netlink_parse(msg_buffer, (size_t)len, &detached, &sys_name, &busnum, &devaddr);
332 	if (r)
333 		return r;
334 
335 	usbi_dbg("netlink hotplug found device busnum: %hhu, devaddr: %hhu, sys_name: %s, removed: %s",
336 		 busnum, devaddr, sys_name, detached ? "yes" : "no");
337 
338 	/* signal device is available (or not) to all contexts */
339 	if (detached)
340 		linux_device_disconnected(busnum, devaddr);
341 	else
342 		linux_hotplug_enumerate(busnum, devaddr, sys_name);
343 
344 	return 0;
345 }
346 
linux_netlink_event_thread_main(void * arg)347 static void *linux_netlink_event_thread_main(void *arg)
348 {
349 	struct pollfd fds[] = {
350 		{ .fd = USBI_EVENT_OS_HANDLE(&netlink_control_event),
351 		  .events = USBI_EVENT_POLL_EVENTS },
352 		{ .fd = linux_netlink_socket,
353 		  .events = POLLIN },
354 	};
355 	int r;
356 
357 	UNUSED(arg);
358 
359 #if defined(HAVE_PTHREAD_SETNAME_NP)
360 	r = pthread_setname_np(pthread_self(), "libusb_event");
361 	if (r)
362 		usbi_warn(NULL, "failed to set hotplug event thread name, error=%d", r);
363 #endif
364 
365 	usbi_dbg("netlink event thread entering");
366 
367 	while (1) {
368 		r = poll(fds, 2, -1);
369 		if (r == -1) {
370 			/* check for temporary failure */
371 			if (errno == EINTR)
372 				continue;
373 			usbi_err(NULL, "poll() failed, errno=%d", errno);
374 			break;
375 		}
376 		if (fds[0].revents) {
377 			/* activity on control event, exit */
378 			break;
379 		}
380 		if (fds[1].revents) {
381 			usbi_mutex_static_lock(&linux_hotplug_lock);
382 			linux_netlink_read_message();
383 			usbi_mutex_static_unlock(&linux_hotplug_lock);
384 		}
385 	}
386 
387 	usbi_dbg("netlink event thread exiting");
388 
389 	return NULL;
390 }
391 
linux_netlink_hotplug_poll(void)392 void linux_netlink_hotplug_poll(void)
393 {
394 	int r;
395 
396 	usbi_mutex_static_lock(&linux_hotplug_lock);
397 	do {
398 		r = linux_netlink_read_message();
399 	} while (r == 0);
400 	usbi_mutex_static_unlock(&linux_hotplug_lock);
401 }
402