]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1+ | |
2 | * | |
3 | * libudev - interface to udev device information | |
4 | * | |
5 | * This library is free software; you can redistribute it and/or | |
6 | * modify it under the terms of the GNU Lesser General Public | |
7 | * License as published by the Free Software Foundation; either | |
8 | * version 2.1 of the License, or (at your option) any later version. | |
9 | */ | |
10 | ||
11 | #include <errno.h> | |
12 | #include <poll.h> | |
13 | #include <stddef.h> | |
14 | #include <stdlib.h> | |
15 | #include <string.h> | |
16 | #include <sys/socket.h> | |
17 | #include <sys/un.h> | |
18 | #include <unistd.h> | |
19 | ||
20 | #include "alloc-util.h" | |
21 | #include "fd-util.h" | |
22 | #include "format-util.h" | |
23 | #include "io-util.h" | |
24 | #include "socket-util.h" | |
25 | #include "strxcpyx.h" | |
26 | #include "udev-ctrl.h" | |
27 | #include "util.h" | |
28 | ||
29 | /* wire protocol magic must match */ | |
30 | #define UDEV_CTRL_MAGIC 0xdead1dea | |
31 | ||
32 | enum udev_ctrl_msg_type { | |
33 | UDEV_CTRL_UNKNOWN, | |
34 | UDEV_CTRL_SET_LOG_LEVEL, | |
35 | UDEV_CTRL_STOP_EXEC_QUEUE, | |
36 | UDEV_CTRL_START_EXEC_QUEUE, | |
37 | UDEV_CTRL_RELOAD, | |
38 | UDEV_CTRL_SET_ENV, | |
39 | UDEV_CTRL_SET_CHILDREN_MAX, | |
40 | UDEV_CTRL_PING, | |
41 | UDEV_CTRL_EXIT, | |
42 | }; | |
43 | ||
44 | struct udev_ctrl_msg_wire { | |
45 | char version[16]; | |
46 | unsigned magic; | |
47 | enum udev_ctrl_msg_type type; | |
48 | union { | |
49 | int intval; | |
50 | char buf[256]; | |
51 | }; | |
52 | }; | |
53 | ||
54 | struct udev_ctrl_msg { | |
55 | unsigned n_ref; | |
56 | struct udev_ctrl_connection *conn; | |
57 | struct udev_ctrl_msg_wire ctrl_msg_wire; | |
58 | }; | |
59 | ||
60 | struct udev_ctrl { | |
61 | unsigned n_ref; | |
62 | int sock; | |
63 | union sockaddr_union saddr; | |
64 | socklen_t addrlen; | |
65 | bool bound; | |
66 | bool cleanup_socket; | |
67 | bool connected; | |
68 | }; | |
69 | ||
70 | struct udev_ctrl_connection { | |
71 | unsigned n_ref; | |
72 | struct udev_ctrl *uctrl; | |
73 | int sock; | |
74 | }; | |
75 | ||
76 | struct udev_ctrl *udev_ctrl_new_from_fd(int fd) { | |
77 | struct udev_ctrl *uctrl; | |
78 | int r; | |
79 | ||
80 | uctrl = new0(struct udev_ctrl, 1); | |
81 | if (!uctrl) | |
82 | return NULL; | |
83 | uctrl->n_ref = 1; | |
84 | ||
85 | if (fd < 0) { | |
86 | uctrl->sock = socket(AF_LOCAL, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0); | |
87 | if (uctrl->sock < 0) { | |
88 | log_error_errno(errno, "Failed to create socket: %m"); | |
89 | udev_ctrl_unref(uctrl); | |
90 | return NULL; | |
91 | } | |
92 | } else { | |
93 | uctrl->bound = true; | |
94 | uctrl->sock = fd; | |
95 | } | |
96 | ||
97 | /* | |
98 | * FIXME: remove it as soon as we can depend on this: | |
99 | * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=90c6bd34f884cd9cee21f1d152baf6c18bcac949 | |
100 | */ | |
101 | r = setsockopt_int(uctrl->sock, SOL_SOCKET, SO_PASSCRED, true); | |
102 | if (r < 0) | |
103 | log_warning_errno(r, "Failed to set SO_PASSCRED: %m"); | |
104 | ||
105 | uctrl->saddr.un = (struct sockaddr_un) { | |
106 | .sun_family = AF_UNIX, | |
107 | .sun_path = "/run/udev/control", | |
108 | }; | |
109 | ||
110 | uctrl->addrlen = SOCKADDR_UN_LEN(uctrl->saddr.un); | |
111 | return uctrl; | |
112 | } | |
113 | ||
114 | struct udev_ctrl *udev_ctrl_new(void) { | |
115 | return udev_ctrl_new_from_fd(-1); | |
116 | } | |
117 | ||
118 | int udev_ctrl_enable_receiving(struct udev_ctrl *uctrl) { | |
119 | int err; | |
120 | ||
121 | if (!uctrl->bound) { | |
122 | err = bind(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen); | |
123 | if (err < 0 && errno == EADDRINUSE) { | |
124 | (void) sockaddr_un_unlink(&uctrl->saddr.un); | |
125 | err = bind(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen); | |
126 | } | |
127 | ||
128 | if (err < 0) | |
129 | return log_error_errno(errno, "Failed to bind socket: %m"); | |
130 | ||
131 | err = listen(uctrl->sock, 0); | |
132 | if (err < 0) | |
133 | return log_error_errno(errno, "Failed to listen: %m"); | |
134 | ||
135 | uctrl->bound = true; | |
136 | uctrl->cleanup_socket = true; | |
137 | } | |
138 | return 0; | |
139 | } | |
140 | ||
141 | static struct udev_ctrl *udev_ctrl_free(struct udev_ctrl *uctrl) { | |
142 | assert(uctrl); | |
143 | ||
144 | safe_close(uctrl->sock); | |
145 | return mfree(uctrl); | |
146 | } | |
147 | ||
148 | DEFINE_PRIVATE_TRIVIAL_REF_FUNC(struct udev_ctrl, udev_ctrl); | |
149 | DEFINE_TRIVIAL_UNREF_FUNC(struct udev_ctrl, udev_ctrl, udev_ctrl_free); | |
150 | ||
151 | int udev_ctrl_cleanup(struct udev_ctrl *uctrl) { | |
152 | if (!uctrl) | |
153 | return 0; | |
154 | if (uctrl->cleanup_socket) | |
155 | sockaddr_un_unlink(&uctrl->saddr.un); | |
156 | return 0; | |
157 | } | |
158 | ||
159 | int udev_ctrl_get_fd(struct udev_ctrl *uctrl) { | |
160 | if (!uctrl) | |
161 | return -EINVAL; | |
162 | return uctrl->sock; | |
163 | } | |
164 | ||
165 | struct udev_ctrl_connection *udev_ctrl_get_connection(struct udev_ctrl *uctrl) { | |
166 | struct udev_ctrl_connection *conn; | |
167 | struct ucred ucred = {}; | |
168 | int r; | |
169 | ||
170 | conn = new(struct udev_ctrl_connection, 1); | |
171 | if (!conn) | |
172 | return NULL; | |
173 | conn->n_ref = 1; | |
174 | conn->uctrl = uctrl; | |
175 | ||
176 | conn->sock = accept4(uctrl->sock, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK); | |
177 | if (conn->sock < 0) { | |
178 | if (errno != EINTR) | |
179 | log_error_errno(errno, "Failed to receive ctrl connection: %m"); | |
180 | goto err; | |
181 | } | |
182 | ||
183 | /* check peer credential of connection */ | |
184 | r = getpeercred(conn->sock, &ucred); | |
185 | if (r < 0) { | |
186 | log_error_errno(r, "Failed to receive credentials of ctrl connection: %m"); | |
187 | goto err; | |
188 | } | |
189 | if (ucred.uid > 0) { | |
190 | log_error("Sender uid="UID_FMT", message ignored", ucred.uid); | |
191 | goto err; | |
192 | } | |
193 | ||
194 | /* enable receiving of the sender credentials in the messages */ | |
195 | r = setsockopt_int(conn->sock, SOL_SOCKET, SO_PASSCRED, true); | |
196 | if (r < 0) | |
197 | log_warning_errno(r, "Failed to set SO_PASSCRED: %m"); | |
198 | ||
199 | udev_ctrl_ref(uctrl); | |
200 | return conn; | |
201 | err: | |
202 | safe_close(conn->sock); | |
203 | return mfree(conn); | |
204 | } | |
205 | ||
206 | static struct udev_ctrl_connection *udev_ctrl_connection_free(struct udev_ctrl_connection *conn) { | |
207 | assert(conn); | |
208 | ||
209 | safe_close(conn->sock); | |
210 | udev_ctrl_unref(conn->uctrl); | |
211 | return mfree(conn); | |
212 | } | |
213 | ||
214 | DEFINE_TRIVIAL_REF_UNREF_FUNC(struct udev_ctrl_connection, udev_ctrl_connection, udev_ctrl_connection_free); | |
215 | ||
216 | static int ctrl_send(struct udev_ctrl *uctrl, enum udev_ctrl_msg_type type, int intval, const char *buf, int timeout) { | |
217 | struct udev_ctrl_msg_wire ctrl_msg_wire = { | |
218 | .version = "udev-" STRINGIFY(PROJECT_VERSION), | |
219 | .magic = UDEV_CTRL_MAGIC, | |
220 | .type = type, | |
221 | }; | |
222 | ||
223 | if (buf) | |
224 | strscpy(ctrl_msg_wire.buf, sizeof(ctrl_msg_wire.buf), buf); | |
225 | else | |
226 | ctrl_msg_wire.intval = intval; | |
227 | ||
228 | if (!uctrl->connected) { | |
229 | if (connect(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen) < 0) | |
230 | return -errno; | |
231 | uctrl->connected = true; | |
232 | } | |
233 | if (send(uctrl->sock, &ctrl_msg_wire, sizeof(ctrl_msg_wire), 0) < 0) | |
234 | return -errno; | |
235 | ||
236 | /* wait for peer message handling or disconnect */ | |
237 | for (;;) { | |
238 | struct pollfd pfd = { | |
239 | .fd = uctrl->sock, | |
240 | .events = POLLIN, | |
241 | }; | |
242 | int r; | |
243 | ||
244 | r = poll(&pfd, 1, timeout * MSEC_PER_SEC); | |
245 | if (r < 0) { | |
246 | if (errno == EINTR) | |
247 | continue; | |
248 | return -errno; | |
249 | } | |
250 | if (r == 0) | |
251 | return -ETIMEDOUT; | |
252 | if (pfd.revents & POLLERR) | |
253 | return -EIO; | |
254 | return 0; | |
255 | } | |
256 | } | |
257 | ||
258 | int udev_ctrl_send_set_log_level(struct udev_ctrl *uctrl, int priority, int timeout) { | |
259 | return ctrl_send(uctrl, UDEV_CTRL_SET_LOG_LEVEL, priority, NULL, timeout); | |
260 | } | |
261 | ||
262 | int udev_ctrl_send_stop_exec_queue(struct udev_ctrl *uctrl, int timeout) { | |
263 | return ctrl_send(uctrl, UDEV_CTRL_STOP_EXEC_QUEUE, 0, NULL, timeout); | |
264 | } | |
265 | ||
266 | int udev_ctrl_send_start_exec_queue(struct udev_ctrl *uctrl, int timeout) { | |
267 | return ctrl_send(uctrl, UDEV_CTRL_START_EXEC_QUEUE, 0, NULL, timeout); | |
268 | } | |
269 | ||
270 | int udev_ctrl_send_reload(struct udev_ctrl *uctrl, int timeout) { | |
271 | return ctrl_send(uctrl, UDEV_CTRL_RELOAD, 0, NULL, timeout); | |
272 | } | |
273 | ||
274 | int udev_ctrl_send_set_env(struct udev_ctrl *uctrl, const char *key, int timeout) { | |
275 | return ctrl_send(uctrl, UDEV_CTRL_SET_ENV, 0, key, timeout); | |
276 | } | |
277 | ||
278 | int udev_ctrl_send_set_children_max(struct udev_ctrl *uctrl, int count, int timeout) { | |
279 | return ctrl_send(uctrl, UDEV_CTRL_SET_CHILDREN_MAX, count, NULL, timeout); | |
280 | } | |
281 | ||
282 | int udev_ctrl_send_ping(struct udev_ctrl *uctrl, int timeout) { | |
283 | return ctrl_send(uctrl, UDEV_CTRL_PING, 0, NULL, timeout); | |
284 | } | |
285 | ||
286 | int udev_ctrl_send_exit(struct udev_ctrl *uctrl, int timeout) { | |
287 | return ctrl_send(uctrl, UDEV_CTRL_EXIT, 0, NULL, timeout); | |
288 | } | |
289 | ||
290 | struct udev_ctrl_msg *udev_ctrl_receive_msg(struct udev_ctrl_connection *conn) { | |
291 | struct udev_ctrl_msg *uctrl_msg; | |
292 | ssize_t size; | |
293 | struct cmsghdr *cmsg; | |
294 | struct iovec iov; | |
295 | char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; | |
296 | struct msghdr smsg = { | |
297 | .msg_iov = &iov, | |
298 | .msg_iovlen = 1, | |
299 | .msg_control = cred_msg, | |
300 | .msg_controllen = sizeof(cred_msg), | |
301 | }; | |
302 | struct ucred *cred; | |
303 | ||
304 | uctrl_msg = new0(struct udev_ctrl_msg, 1); | |
305 | if (!uctrl_msg) | |
306 | return NULL; | |
307 | uctrl_msg->n_ref = 1; | |
308 | uctrl_msg->conn = conn; | |
309 | udev_ctrl_connection_ref(conn); | |
310 | ||
311 | /* wait for the incoming message */ | |
312 | for (;;) { | |
313 | struct pollfd pfd[1]; | |
314 | int r; | |
315 | ||
316 | pfd[0].fd = conn->sock; | |
317 | pfd[0].events = POLLIN; | |
318 | ||
319 | r = poll(pfd, 1, 10000); | |
320 | if (r < 0) { | |
321 | if (errno == EINTR) | |
322 | continue; | |
323 | goto err; | |
324 | } else if (r == 0) { | |
325 | log_error("Timeout waiting for ctrl message"); | |
326 | goto err; | |
327 | } else { | |
328 | if (!(pfd[0].revents & POLLIN)) { | |
329 | log_error("Invalid ctrl connection: %m"); | |
330 | goto err; | |
331 | } | |
332 | } | |
333 | ||
334 | break; | |
335 | } | |
336 | ||
337 | iov = IOVEC_MAKE(&uctrl_msg->ctrl_msg_wire, sizeof(struct udev_ctrl_msg_wire)); | |
338 | ||
339 | size = recvmsg(conn->sock, &smsg, 0); | |
340 | if (size < 0) { | |
341 | log_error_errno(errno, "Failed to receive ctrl message: %m"); | |
342 | goto err; | |
343 | } | |
344 | ||
345 | cmsg_close_all(&smsg); | |
346 | ||
347 | cmsg = CMSG_FIRSTHDR(&smsg); | |
348 | ||
349 | if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) { | |
350 | log_error("No sender credentials received, ignoring message"); | |
351 | goto err; | |
352 | } | |
353 | ||
354 | cred = (struct ucred *) CMSG_DATA(cmsg); | |
355 | ||
356 | if (cred->uid != 0) { | |
357 | log_error("Sender uid="UID_FMT", ignoring message", cred->uid); | |
358 | goto err; | |
359 | } | |
360 | ||
361 | if (uctrl_msg->ctrl_msg_wire.magic != UDEV_CTRL_MAGIC) { | |
362 | log_error("Message magic 0x%08x doesn't match, ignoring", uctrl_msg->ctrl_msg_wire.magic); | |
363 | goto err; | |
364 | } | |
365 | ||
366 | return uctrl_msg; | |
367 | err: | |
368 | udev_ctrl_msg_unref(uctrl_msg); | |
369 | return NULL; | |
370 | } | |
371 | ||
372 | static struct udev_ctrl_msg *udev_ctrl_msg_free(struct udev_ctrl_msg *ctrl_msg) { | |
373 | assert(ctrl_msg); | |
374 | ||
375 | udev_ctrl_connection_unref(ctrl_msg->conn); | |
376 | return mfree(ctrl_msg); | |
377 | } | |
378 | ||
379 | DEFINE_TRIVIAL_UNREF_FUNC(struct udev_ctrl_msg, udev_ctrl_msg, udev_ctrl_msg_free); | |
380 | ||
381 | int udev_ctrl_get_set_log_level(struct udev_ctrl_msg *ctrl_msg) { | |
382 | if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_LOG_LEVEL) | |
383 | return ctrl_msg->ctrl_msg_wire.intval; | |
384 | return -1; | |
385 | } | |
386 | ||
387 | int udev_ctrl_get_stop_exec_queue(struct udev_ctrl_msg *ctrl_msg) { | |
388 | if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_STOP_EXEC_QUEUE) | |
389 | return 1; | |
390 | return -1; | |
391 | } | |
392 | ||
393 | int udev_ctrl_get_start_exec_queue(struct udev_ctrl_msg *ctrl_msg) { | |
394 | if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_START_EXEC_QUEUE) | |
395 | return 1; | |
396 | return -1; | |
397 | } | |
398 | ||
399 | int udev_ctrl_get_reload(struct udev_ctrl_msg *ctrl_msg) { | |
400 | if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_RELOAD) | |
401 | return 1; | |
402 | return -1; | |
403 | } | |
404 | ||
405 | const char *udev_ctrl_get_set_env(struct udev_ctrl_msg *ctrl_msg) { | |
406 | if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_ENV) | |
407 | return ctrl_msg->ctrl_msg_wire.buf; | |
408 | return NULL; | |
409 | } | |
410 | ||
411 | int udev_ctrl_get_set_children_max(struct udev_ctrl_msg *ctrl_msg) { | |
412 | if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_SET_CHILDREN_MAX) | |
413 | return ctrl_msg->ctrl_msg_wire.intval; | |
414 | return -1; | |
415 | } | |
416 | ||
417 | int udev_ctrl_get_ping(struct udev_ctrl_msg *ctrl_msg) { | |
418 | if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_PING) | |
419 | return 1; | |
420 | return -1; | |
421 | } | |
422 | ||
423 | int udev_ctrl_get_exit(struct udev_ctrl_msg *ctrl_msg) { | |
424 | if (ctrl_msg->ctrl_msg_wire.type == UDEV_CTRL_EXIT) | |
425 | return 1; | |
426 | return -1; | |
427 | } |