]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/udev-ctrl.c
tree-wide: use ASSERT_PTR more
[thirdparty/systemd.git] / src / udev / udev-ctrl.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <poll.h>
5 #include <stddef.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/un.h>
9 #include <unistd.h>
10
11 #include "sd-event.h"
12
13 #include "alloc-util.h"
14 #include "errno-util.h"
15 #include "fd-util.h"
16 #include "format-util.h"
17 #include "io-util.h"
18 #include "socket-util.h"
19 #include "strxcpyx.h"
20 #include "udev-ctrl.h"
21 #include "util.h"
22
23 /* wire protocol magic must match */
24 #define UDEV_CTRL_MAGIC 0xdead1dea
25
26 typedef struct UdevCtrlMessageWire {
27 char version[16];
28 unsigned magic;
29 UdevCtrlMessageType type;
30 UdevCtrlMessageValue value;
31 } UdevCtrlMessageWire;
32
33 struct UdevCtrl {
34 unsigned n_ref;
35 int sock;
36 int sock_connect;
37 union sockaddr_union saddr;
38 socklen_t addrlen;
39 bool bound;
40 bool connected;
41 bool maybe_disconnected;
42 sd_event *event;
43 sd_event_source *event_source;
44 sd_event_source *event_source_connect;
45 udev_ctrl_handler_t callback;
46 void *userdata;
47 };
48
49 int udev_ctrl_new_from_fd(UdevCtrl **ret, int fd) {
50 _cleanup_close_ int sock = -1;
51 UdevCtrl *uctrl;
52
53 assert(ret);
54
55 if (fd < 0) {
56 sock = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
57 if (sock < 0)
58 return log_error_errno(errno, "Failed to create socket: %m");
59 }
60
61 uctrl = new(UdevCtrl, 1);
62 if (!uctrl)
63 return -ENOMEM;
64
65 *uctrl = (UdevCtrl) {
66 .n_ref = 1,
67 .sock = fd >= 0 ? fd : TAKE_FD(sock),
68 .sock_connect = -1,
69 .bound = fd >= 0,
70 };
71
72 uctrl->saddr.un = (struct sockaddr_un) {
73 .sun_family = AF_UNIX,
74 .sun_path = "/run/udev/control",
75 };
76
77 uctrl->addrlen = SOCKADDR_UN_LEN(uctrl->saddr.un);
78
79 *ret = TAKE_PTR(uctrl);
80 return 0;
81 }
82
83 int udev_ctrl_enable_receiving(UdevCtrl *uctrl) {
84 assert(uctrl);
85
86 if (uctrl->bound)
87 return 0;
88
89 (void) sockaddr_un_unlink(&uctrl->saddr.un);
90 if (bind(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen) < 0)
91 return log_error_errno(errno, "Failed to bind udev control socket: %m");
92
93 if (listen(uctrl->sock, 0) < 0)
94 return log_error_errno(errno, "Failed to listen udev control socket: %m");
95
96 uctrl->bound = true;
97 return 0;
98 }
99
100 static void udev_ctrl_disconnect(UdevCtrl *uctrl) {
101 if (!uctrl)
102 return;
103
104 uctrl->event_source_connect = sd_event_source_unref(uctrl->event_source_connect);
105 uctrl->sock_connect = safe_close(uctrl->sock_connect);
106 }
107
108 static UdevCtrl *udev_ctrl_free(UdevCtrl *uctrl) {
109 assert(uctrl);
110
111 udev_ctrl_disconnect(uctrl);
112
113 sd_event_source_unref(uctrl->event_source);
114 safe_close(uctrl->sock);
115
116 sd_event_unref(uctrl->event);
117 return mfree(uctrl);
118 }
119
120 DEFINE_TRIVIAL_REF_UNREF_FUNC(UdevCtrl, udev_ctrl, udev_ctrl_free);
121
122 int udev_ctrl_attach_event(UdevCtrl *uctrl, sd_event *event) {
123 int r;
124
125 assert_return(uctrl, -EINVAL);
126 assert_return(!uctrl->event, -EBUSY);
127
128 if (event)
129 uctrl->event = sd_event_ref(event);
130 else {
131 r = sd_event_default(&uctrl->event);
132 if (r < 0)
133 return r;
134 }
135
136 return 0;
137 }
138
139 sd_event_source *udev_ctrl_get_event_source(UdevCtrl *uctrl) {
140 assert(uctrl);
141
142 return uctrl->event_source;
143 }
144
145 static void udev_ctrl_disconnect_and_listen_again(UdevCtrl *uctrl) {
146 udev_ctrl_disconnect(uctrl);
147 udev_ctrl_unref(uctrl);
148 (void) sd_event_source_set_enabled(uctrl->event_source, SD_EVENT_ON);
149 /* We don't return NULL here because uctrl is not freed */
150 }
151
152 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UdevCtrl*, udev_ctrl_disconnect_and_listen_again, NULL);
153
154 static int udev_ctrl_connection_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
155 _cleanup_(udev_ctrl_disconnect_and_listen_againp) UdevCtrl *uctrl = NULL;
156 UdevCtrlMessageWire msg_wire;
157 struct iovec iov = IOVEC_MAKE(&msg_wire, sizeof(UdevCtrlMessageWire));
158 CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control;
159 struct msghdr smsg = {
160 .msg_iov = &iov,
161 .msg_iovlen = 1,
162 .msg_control = &control,
163 .msg_controllen = sizeof(control),
164 };
165 struct cmsghdr *cmsg;
166 struct ucred *cred;
167 ssize_t size;
168
169 assert(userdata);
170
171 /* When UDEV_CTRL_EXIT is received, manager unref udev_ctrl object.
172 * To avoid the object freed, let's increment the refcount. */
173 uctrl = udev_ctrl_ref(userdata);
174
175 size = next_datagram_size_fd(fd);
176 if (size < 0)
177 return log_error_errno(size, "Failed to get size of message: %m");
178 if (size == 0)
179 return 0; /* Client disconnects? */
180
181 size = recvmsg_safe(fd, &smsg, 0);
182 if (size == -EINTR)
183 return 0;
184 if (size < 0)
185 return log_error_errno(size, "Failed to receive ctrl message: %m");
186
187 cmsg_close_all(&smsg);
188
189 cmsg = CMSG_FIRSTHDR(&smsg);
190
191 if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) {
192 log_error("No sender credentials received, ignoring message");
193 return 0;
194 }
195
196 cred = (struct ucred *) CMSG_DATA(cmsg);
197
198 if (cred->uid != 0) {
199 log_error("Invalid sender uid "UID_FMT", ignoring message", cred->uid);
200 return 0;
201 }
202
203 if (msg_wire.magic != UDEV_CTRL_MAGIC) {
204 log_error("Message magic 0x%08x doesn't match, ignoring message", msg_wire.magic);
205 return 0;
206 }
207
208 if (msg_wire.type == _UDEV_CTRL_END_MESSAGES)
209 return 0;
210
211 if (uctrl->callback)
212 (void) uctrl->callback(uctrl, msg_wire.type, &msg_wire.value, uctrl->userdata);
213
214 /* Do not disconnect and wait for next message. */
215 uctrl = udev_ctrl_unref(uctrl);
216 return 0;
217 }
218
219 static int udev_ctrl_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
220 UdevCtrl *uctrl = ASSERT_PTR(userdata);
221 _cleanup_close_ int sock = -1;
222 struct ucred ucred;
223 int r;
224
225 sock = accept4(fd, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK);
226 if (sock < 0) {
227 if (ERRNO_IS_ACCEPT_AGAIN(errno))
228 return 0;
229
230 return log_error_errno(errno, "Failed to accept ctrl connection: %m");
231 }
232
233 /* check peer credential of connection */
234 r = getpeercred(sock, &ucred);
235 if (r < 0) {
236 log_error_errno(r, "Failed to receive credentials of ctrl connection: %m");
237 return 0;
238 }
239
240 if (ucred.uid > 0) {
241 log_error("Invalid sender uid "UID_FMT", closing connection", ucred.uid);
242 return 0;
243 }
244
245 /* enable receiving of the sender credentials in the messages */
246 r = setsockopt_int(sock, SOL_SOCKET, SO_PASSCRED, true);
247 if (r < 0)
248 log_warning_errno(r, "Failed to set SO_PASSCRED, ignoring: %m");
249
250 r = sd_event_add_io(uctrl->event, &uctrl->event_source_connect, sock, EPOLLIN, udev_ctrl_connection_event_handler, uctrl);
251 if (r < 0) {
252 log_error_errno(r, "Failed to create event source for udev control connection: %m");
253 return 0;
254 }
255
256 (void) sd_event_source_set_description(uctrl->event_source_connect, "udev-ctrl-connection");
257
258 /* Do not accept multiple connection. */
259 (void) sd_event_source_set_enabled(uctrl->event_source, SD_EVENT_OFF);
260
261 uctrl->sock_connect = TAKE_FD(sock);
262 return 0;
263 }
264
265 int udev_ctrl_start(UdevCtrl *uctrl, udev_ctrl_handler_t callback, void *userdata) {
266 int r;
267
268 assert(uctrl);
269
270 if (!uctrl->event) {
271 r = udev_ctrl_attach_event(uctrl, NULL);
272 if (r < 0)
273 return r;
274 }
275
276 r = udev_ctrl_enable_receiving(uctrl);
277 if (r < 0)
278 return r;
279
280 uctrl->callback = callback;
281 uctrl->userdata = userdata;
282
283 r = sd_event_add_io(uctrl->event, &uctrl->event_source, uctrl->sock, EPOLLIN, udev_ctrl_event_handler, uctrl);
284 if (r < 0)
285 return r;
286
287 (void) sd_event_source_set_description(uctrl->event_source, "udev-ctrl");
288
289 return 0;
290 }
291
292 int udev_ctrl_send(UdevCtrl *uctrl, UdevCtrlMessageType type, const void *data) {
293 UdevCtrlMessageWire ctrl_msg_wire = {
294 .version = "udev-" STRINGIFY(PROJECT_VERSION),
295 .magic = UDEV_CTRL_MAGIC,
296 .type = type,
297 };
298
299 if (uctrl->maybe_disconnected)
300 return -ENOANO; /* to distinguish this from other errors. */
301
302 if (type == UDEV_CTRL_SET_ENV) {
303 assert(data);
304 strscpy(ctrl_msg_wire.value.buf, sizeof(ctrl_msg_wire.value.buf), data);
305 } else if (IN_SET(type, UDEV_CTRL_SET_LOG_LEVEL, UDEV_CTRL_SET_CHILDREN_MAX))
306 ctrl_msg_wire.value.intval = PTR_TO_INT(data);
307
308 if (!uctrl->connected) {
309 if (connect(uctrl->sock, &uctrl->saddr.sa, uctrl->addrlen) < 0)
310 return -errno;
311 uctrl->connected = true;
312 }
313
314 if (send(uctrl->sock, &ctrl_msg_wire, sizeof(ctrl_msg_wire), 0) < 0)
315 return -errno;
316
317 if (type == UDEV_CTRL_EXIT)
318 uctrl->maybe_disconnected = true;
319
320 return 0;
321 }
322
323 int udev_ctrl_wait(UdevCtrl *uctrl, usec_t timeout) {
324 _cleanup_(sd_event_source_disable_unrefp) sd_event_source *source_io = NULL, *source_timeout = NULL;
325 int r;
326
327 assert(uctrl);
328
329 if (uctrl->sock < 0)
330 return 0;
331 if (!uctrl->connected)
332 return 0;
333
334 if (!uctrl->maybe_disconnected) {
335 r = udev_ctrl_send(uctrl, _UDEV_CTRL_END_MESSAGES, NULL);
336 if (r < 0)
337 return r;
338 }
339
340 if (timeout == 0)
341 return 0;
342
343 if (!uctrl->event) {
344 r = udev_ctrl_attach_event(uctrl, NULL);
345 if (r < 0)
346 return r;
347 }
348
349 r = sd_event_add_io(uctrl->event, &source_io, uctrl->sock, EPOLLIN, NULL, INT_TO_PTR(0));
350 if (r < 0)
351 return r;
352
353 (void) sd_event_source_set_description(source_io, "udev-ctrl-wait-io");
354
355 if (timeout != USEC_INFINITY) {
356 r = sd_event_add_time_relative(
357 uctrl->event, &source_timeout, CLOCK_BOOTTIME,
358 timeout,
359 0, NULL, INT_TO_PTR(-ETIMEDOUT));
360 if (r < 0)
361 return r;
362
363 (void) sd_event_source_set_description(source_timeout, "udev-ctrl-wait-timeout");
364 }
365
366 return sd_event_loop(uctrl->event);
367 }