]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/rfkill/rfkill.c
tree-wide: drop license boilerplate
[thirdparty/systemd.git] / src / rfkill / rfkill.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
3990f247
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2013 Lennart Poettering
3990f247
LP
6***/
7
d35c1bb1
LP
8#include <linux/rfkill.h>
9#include <poll.h>
10
b4bbcaa9 11#include "libudev.h"
d35c1bb1
LP
12#include "sd-daemon.h"
13
b5efdb8a 14#include "alloc-util.h"
4f5dd394 15#include "escape.h"
3ffd4af2 16#include "fd-util.h"
d35c1bb1 17#include "fileio.h"
c004493c 18#include "io-util.h"
d35c1bb1 19#include "mkdir.h"
6bedfcbb 20#include "parse-util.h"
4e731273 21#include "proc-cmdline.h"
8b43440b 22#include "string-table.h"
07630cea 23#include "string-util.h"
3990f247 24#include "udev-util.h"
d35c1bb1 25#include "util.h"
202cb8c3 26#include "list.h"
3990f247 27
202cb8c3
BB
28/* Note that any write is delayed until exit and the rfkill state will not be
29 * stored for rfkill indices that disappear after a change. */
d35c1bb1 30#define EXIT_USEC (5 * USEC_PER_SEC)
3990f247 31
202cb8c3
BB
32typedef struct write_queue_item {
33 LIST_FIELDS(struct write_queue_item, queue);
34 int rfkill_idx;
35 char *file;
36 int state;
37} write_queue_item;
38
3b3d1737
LP
39static struct write_queue_item* write_queue_item_free(struct write_queue_item *item) {
40 if (!item)
41 return NULL;
202cb8c3
BB
42
43 free(item->file);
3b3d1737 44 return mfree(item);
202cb8c3
BB
45}
46
d35c1bb1
LP
47static const char* const rfkill_type_table[NUM_RFKILL_TYPES] = {
48 [RFKILL_TYPE_ALL] = "all",
49 [RFKILL_TYPE_WLAN] = "wlan",
50 [RFKILL_TYPE_BLUETOOTH] = "bluetooth",
51 [RFKILL_TYPE_UWB] = "uwb",
52 [RFKILL_TYPE_WIMAX] = "wimax",
53 [RFKILL_TYPE_WWAN] = "wwan",
54 [RFKILL_TYPE_GPS] = "gps",
ac9455ef
TA
55 [RFKILL_TYPE_FM] = "fm",
56 [RFKILL_TYPE_NFC] = "nfc",
d35c1bb1 57};
3990f247 58
d35c1bb1 59DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int);
3990f247 60
d35c1bb1
LP
61static int find_device(
62 struct udev *udev,
63 const struct rfkill_event *event,
64 struct udev_device **ret) {
3990f247 65
d35c1bb1
LP
66 _cleanup_free_ char *sysname = NULL;
67 struct udev_device *device;
68 const char *name;
3990f247 69
d35c1bb1
LP
70 assert(udev);
71 assert(event);
72 assert(ret);
3990f247 73
d35c1bb1
LP
74 if (asprintf(&sysname, "rfkill%i", event->idx) < 0)
75 return log_oom();
76
77 device = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
78 if (!device)
bc5e002e
LP
79 return log_full_errno(IN_SET(errno, ENOENT, ENXIO, ENODEV) ? LOG_DEBUG : LOG_ERR, errno,
80 "Failed to open device '%s': %m", sysname);
3990f247
LP
81
82 name = udev_device_get_sysattr_value(device, "name");
83 if (!name) {
d35c1bb1
LP
84 log_debug("Device has no name, ignoring.");
85 udev_device_unref(device);
86 return -ENOENT;
4844262f
LP
87 }
88
89 log_debug("Operating on rfkill device '%s'.", name);
90
d35c1bb1
LP
91 *ret = device;
92 return 0;
93}
94
95static int wait_for_initialized(
96 struct udev *udev,
97 struct udev_device *device,
98 struct udev_device **ret) {
99
100 _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL;
101 struct udev_device *d;
102 const char *sysname;
103 int watch_fd, r;
104
105 assert(udev);
106 assert(device);
107 assert(ret);
108
109 if (udev_device_get_is_initialized(device) != 0) {
110 *ret = udev_device_ref(device);
111 return 0;
3990f247
LP
112 }
113
d35c1bb1
LP
114 assert_se(sysname = udev_device_get_sysname(device));
115
116 /* Wait until the device is initialized, so that we can get
117 * access to the ID_PATH property */
118
119 monitor = udev_monitor_new_from_netlink(udev, "udev");
120 if (!monitor)
121 return log_error_errno(errno, "Failed to acquire monitor: %m");
122
123 r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "rfkill", NULL);
124 if (r < 0)
125 return log_error_errno(r, "Failed to add rfkill udev match to monitor: %m");
126
127 r = udev_monitor_enable_receiving(monitor);
128 if (r < 0)
129 return log_error_errno(r, "Failed to enable udev receiving: %m");
130
131 watch_fd = udev_monitor_get_fd(monitor);
132 if (watch_fd < 0)
133 return log_error_errno(watch_fd, "Failed to get watch fd: %m");
134
135 /* Check again, maybe things changed */
136 d = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
137 if (!d)
bc5e002e
LP
138 return log_full_errno(IN_SET(errno, ENOENT, ENXIO, ENODEV) ? LOG_DEBUG : LOG_ERR, errno,
139 "Failed to open device '%s': %m", sysname);
d35c1bb1
LP
140
141 if (udev_device_get_is_initialized(d) != 0) {
142 *ret = d;
143 return 0;
144 }
145
146 for (;;) {
147 _cleanup_udev_device_unref_ struct udev_device *t = NULL;
148
8ec1a079 149 r = fd_wait_for_event(watch_fd, POLLIN, EXIT_USEC);
d35c1bb1
LP
150 if (r == -EINTR)
151 continue;
152 if (r < 0)
153 return log_error_errno(r, "Failed to watch udev monitor: %m");
8ec1a079 154 if (r == 0) {
c7f6ca93 155 log_error("Timed out waiting for udev monitor.");
8ec1a079
F
156 return -ETIMEDOUT;
157 }
d35c1bb1
LP
158
159 t = udev_monitor_receive_device(monitor);
160 if (!t)
161 continue;
162
8ec1a079 163 if (streq_ptr(udev_device_get_sysname(t), sysname)) {
d35c1bb1
LP
164 *ret = udev_device_ref(t);
165 return 0;
166 }
3990f247 167 }
d35c1bb1
LP
168}
169
170static int determine_state_file(
171 struct udev *udev,
172 const struct rfkill_event *event,
d35c1bb1
LP
173 char **ret) {
174
8e707663 175 _cleanup_udev_device_unref_ struct udev_device *d = NULL;
d35c1bb1
LP
176 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
177 const char *path_id, *type;
178 char *state_file;
179 int r;
180
181 assert(event);
d35c1bb1
LP
182 assert(ret);
183
8e707663
BB
184 r = find_device(udev, event, &d);
185 if (r < 0)
186 return r;
187
d35c1bb1
LP
188 r = wait_for_initialized(udev, d, &device);
189 if (r < 0)
190 return r;
191
192 assert_se(type = rfkill_type_to_string(event->type));
3990f247 193
f6f738db
LP
194 path_id = udev_device_get_property_value(device, "ID_PATH");
195 if (path_id) {
d35c1bb1
LP
196 _cleanup_free_ char *escaped_path_id = NULL;
197
f6f738db 198 escaped_path_id = cescape(path_id);
d35c1bb1
LP
199 if (!escaped_path_id)
200 return log_oom();
f6f738db 201
605405c6 202 state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type);
f6f738db 203 } else
605405c6 204 state_file = strjoin("/var/lib/systemd/rfkill/", type);
d35c1bb1
LP
205
206 if (!state_file)
207 return log_oom();
208
209 *ret = state_file;
210 return 0;
211}
212
213static int load_state(
214 int rfkill_fd,
215 struct udev *udev,
216 const struct rfkill_event *event) {
217
d35c1bb1
LP
218 _cleanup_free_ char *state_file = NULL, *value = NULL;
219 struct rfkill_event we;
220 ssize_t l;
221 int b, r;
f6f738db 222
d35c1bb1
LP
223 assert(rfkill_fd >= 0);
224 assert(udev);
225 assert(event);
226
934ae16b 227 if (shall_restore_state() == 0)
d35c1bb1
LP
228 return 0;
229
8e707663 230 r = determine_state_file(udev, event, &state_file);
d35c1bb1
LP
231 if (r < 0)
232 return r;
233
234 r = read_one_line_file(state_file, &value);
235 if (r == -ENOENT) {
236 /* No state file? Then save the current state */
237
238 r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
239 if (r < 0)
240 return log_error_errno(r, "Failed to write state file %s: %m", state_file);
241
242 log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file);
243 return 0;
244 }
245 if (r < 0)
246 return log_error_errno(r, "Failed to read state file %s: %m", state_file);
247
248 b = parse_boolean(value);
249 if (b < 0)
250 return log_error_errno(b, "Failed to parse state file %s: %m", state_file);
251
252 we = (struct rfkill_event) {
253 .op = RFKILL_OP_CHANGE,
254 .idx = event->idx,
255 .soft = b,
256 };
257
258 l = write(rfkill_fd, &we, sizeof(we));
259 if (l < 0)
260 return log_error_errno(errno, "Failed to restore rfkill state for %i: %m", event->idx);
261 if (l != sizeof(we)) {
262 log_error("Couldn't write rfkill event structure, too short.");
263 return -EIO;
264 }
265
266 log_debug("Loaded state '%s' from %s.", one_zero(b), state_file);
267 return 0;
268}
269
202cb8c3
BB
270static void save_state_queue_remove(
271 struct write_queue_item **write_queue,
272 int idx,
273 char *state_file) {
274
275 struct write_queue_item *item, *tmp;
276
277 LIST_FOREACH_SAFE(queue, item, tmp, *write_queue) {
278 if ((state_file && streq(item->file, state_file)) || idx == item->rfkill_idx) {
279 log_debug("Canceled previous save state of '%s' to %s.", one_zero(item->state), item->file);
280 LIST_REMOVE(queue, *write_queue, item);
281 write_queue_item_free(item);
282 }
283 }
284}
285
286static int save_state_queue(
287 struct write_queue_item **write_queue,
d35c1bb1
LP
288 int rfkill_fd,
289 struct udev *udev,
290 const struct rfkill_event *event) {
291
d35c1bb1 292 _cleanup_free_ char *state_file = NULL;
202cb8c3 293 struct write_queue_item *item;
d35c1bb1
LP
294 int r;
295
296 assert(rfkill_fd >= 0);
297 assert(udev);
298 assert(event);
299
8e707663 300 r = determine_state_file(udev, event, &state_file);
d35c1bb1
LP
301 if (r < 0)
302 return r;
bc5e002e 303
202cb8c3 304 save_state_queue_remove(write_queue, event->idx, state_file);
d35c1bb1 305
202cb8c3
BB
306 item = new0(struct write_queue_item, 1);
307 if (!item)
308 return -ENOMEM;
309
1cc6c93a 310 item->file = TAKE_PTR(state_file);
202cb8c3
BB
311 item->rfkill_idx = event->idx;
312 item->state = event->soft;
202cb8c3
BB
313
314 LIST_APPEND(queue, *write_queue, item);
315
316 return 0;
317}
318
319static int save_state_cancel(
320 struct write_queue_item **write_queue,
321 int rfkill_fd,
322 struct udev *udev,
323 const struct rfkill_event *event) {
324
325 _cleanup_free_ char *state_file = NULL;
326 int r;
327
328 assert(rfkill_fd >= 0);
329 assert(udev);
330 assert(event);
331
332 r = determine_state_file(udev, event, &state_file);
333 save_state_queue_remove(write_queue, event->idx, state_file);
d35c1bb1 334 if (r < 0)
202cb8c3 335 return r;
d35c1bb1 336
d35c1bb1
LP
337 return 0;
338}
339
202cb8c3
BB
340static int save_state_write(struct write_queue_item **write_queue) {
341 struct write_queue_item *item, *tmp;
342 int result = 0;
343 bool error_logged = false;
344 int r;
345
346 LIST_FOREACH_SAFE(queue, item, tmp, *write_queue) {
347 r = write_string_file(item->file, one_zero(item->state), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
348 if (r < 0) {
349 result = r;
350 if (!error_logged) {
351 log_error_errno(r, "Failed to write state file %s: %m", item->file);
352 error_logged = true;
353 } else
354 log_warning_errno(r, "Failed to write state file %s: %m", item->file);
355 } else
356 log_debug("Saved state '%s' to %s.", one_zero(item->state), item->file);
357
358 LIST_REMOVE(queue, *write_queue, item);
359 write_queue_item_free(item);
360 }
361 return result;
362}
363
d35c1bb1 364int main(int argc, char *argv[]) {
202cb8c3 365 LIST_HEAD(write_queue_item, write_queue);
d35c1bb1
LP
366 _cleanup_udev_unref_ struct udev *udev = NULL;
367 _cleanup_close_ int rfkill_fd = -1;
368 bool ready = false;
369 int r, n;
370
371 if (argc > 1) {
372 log_error("This program requires no arguments.");
3990f247
LP
373 return EXIT_FAILURE;
374 }
375
202cb8c3
BB
376 LIST_HEAD_INIT(write_queue);
377
d35c1bb1
LP
378 log_set_target(LOG_TARGET_AUTO);
379 log_parse_environment();
380 log_open();
3990f247 381
d35c1bb1 382 umask(0022);
354806fb 383
d35c1bb1
LP
384 udev = udev_new();
385 if (!udev) {
386 r = log_oom();
387 goto finish;
388 }
389
390 r = mkdir_p("/var/lib/systemd/rfkill", 0755);
391 if (r < 0) {
392 log_error_errno(r, "Failed to create rfkill directory: %m");
393 goto finish;
394 }
395
396 n = sd_listen_fds(false);
397 if (n < 0) {
398 r = log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m");
399 goto finish;
400 }
401 if (n > 1) {
402 log_error("Got too many file descriptors.");
403 r = -EINVAL;
404 goto finish;
405 }
406
407 if (n == 0) {
408 rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
409 if (rfkill_fd < 0) {
410 if (errno == ENOENT) {
411 log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting.");
412 r = 0;
413 goto finish;
414 }
415
416 r = log_error_errno(errno, "Failed to open /dev/rfkill: %m");
417 goto finish;
3990f247 418 }
d35c1bb1
LP
419 } else {
420 rfkill_fd = SD_LISTEN_FDS_START;
3990f247 421
d35c1bb1 422 r = fd_nonblock(rfkill_fd, 1);
3990f247 423 if (r < 0) {
d35c1bb1
LP
424 log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m");
425 goto finish;
3990f247 426 }
d35c1bb1
LP
427 }
428
429 for (;;) {
430 struct rfkill_event event;
431 const char *type;
432 ssize_t l;
3990f247 433
d35c1bb1
LP
434 l = read(rfkill_fd, &event, sizeof(event));
435 if (l < 0) {
436 if (errno == EAGAIN) {
3990f247 437
d35c1bb1
LP
438 if (!ready) {
439 /* Notify manager that we are
440 * now finished with
441 * processing whatever was
442 * queued */
443 (void) sd_notify(false, "READY=1");
444 ready = true;
445 }
446
447 /* Hang around for a bit, maybe there's more coming */
448
449 r = fd_wait_for_event(rfkill_fd, POLLIN, EXIT_USEC);
450 if (r == -EINTR)
451 continue;
452 if (r < 0) {
453 log_error_errno(r, "Failed to poll() on device: %m");
454 goto finish;
455 }
456 if (r > 0)
457 continue;
458
459 log_debug("All events read and idle, exiting.");
460 break;
461 }
462
463 log_error_errno(errno, "Failed to read from /dev/rfkill: %m");
3990f247
LP
464 }
465
d35c1bb1
LP
466 if (l != RFKILL_EVENT_SIZE_V1) {
467 log_error("Read event structure of invalid size.");
468 r = -EIO;
469 goto finish;
3990f247
LP
470 }
471
d35c1bb1
LP
472 type = rfkill_type_to_string(event.type);
473 if (!type) {
474 log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type);
475 continue;
476 }
477
478 switch (event.op) {
479
480 case RFKILL_OP_ADD:
481 log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type);
482 (void) load_state(rfkill_fd, udev, &event);
483 break;
484
485 case RFKILL_OP_DEL:
486 log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type);
202cb8c3 487 (void) save_state_cancel(&write_queue, rfkill_fd, udev, &event);
d35c1bb1
LP
488 break;
489
490 case RFKILL_OP_CHANGE:
491 log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type);
202cb8c3 492 (void) save_state_queue(&write_queue, rfkill_fd, udev, &event);
d35c1bb1
LP
493 break;
494
495 default:
496 log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type);
497 break;
498 }
3990f247
LP
499 }
500
d35c1bb1
LP
501 r = 0;
502
503finish:
202cb8c3
BB
504 (void) save_state_write(&write_queue);
505
d35c1bb1 506 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
3990f247 507}