]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/rfkill/rfkill.c
Merge pull request #22791 from keszybz/bootctl-invert-order
[thirdparty/systemd.git] / src / rfkill / rfkill.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
3990f247 2
ca78ad1d 3#include <fcntl.h>
d35c1bb1
LP
4#include <linux/rfkill.h>
5#include <poll.h>
ca78ad1d
ZJS
6#include <sys/stat.h>
7#include <sys/types.h>
8#include <unistd.h>
d35c1bb1 9
d35c1bb1 10#include "sd-daemon.h"
21384b81 11#include "sd-device.h"
d35c1bb1 12
b5efdb8a 13#include "alloc-util.h"
94ad3225 14#include "device-util.h"
4f5dd394 15#include "escape.h"
3ffd4af2 16#include "fd-util.h"
d35c1bb1 17#include "fileio.h"
c004493c 18#include "io-util.h"
2bfa8466 19#include "list.h"
7a83c3ae 20#include "main-func.h"
d35c1bb1 21#include "mkdir.h"
6bedfcbb 22#include "parse-util.h"
2bfa8466 23#include "reboot-util.h"
8b43440b 24#include "string-table.h"
07630cea 25#include "string-util.h"
ed435031 26#include "udev-util.h"
d35c1bb1 27#include "util.h"
3990f247 28
202cb8c3
BB
29/* Note that any write is delayed until exit and the rfkill state will not be
30 * stored for rfkill indices that disappear after a change. */
d35c1bb1 31#define EXIT_USEC (5 * USEC_PER_SEC)
3990f247 32
202cb8c3
BB
33typedef struct write_queue_item {
34 LIST_FIELDS(struct write_queue_item, queue);
35 int rfkill_idx;
36 char *file;
37 int state;
38} write_queue_item;
39
5f5f0afc
YW
40typedef struct Context {
41 LIST_HEAD(write_queue_item, write_queue);
42 int rfkill_fd;
43} Context;
44
3b3d1737
LP
45static struct write_queue_item* write_queue_item_free(struct write_queue_item *item) {
46 if (!item)
47 return NULL;
202cb8c3
BB
48
49 free(item->file);
3b3d1737 50 return mfree(item);
202cb8c3
BB
51}
52
d35c1bb1
LP
53static const char* const rfkill_type_table[NUM_RFKILL_TYPES] = {
54 [RFKILL_TYPE_ALL] = "all",
55 [RFKILL_TYPE_WLAN] = "wlan",
56 [RFKILL_TYPE_BLUETOOTH] = "bluetooth",
57 [RFKILL_TYPE_UWB] = "uwb",
58 [RFKILL_TYPE_WIMAX] = "wimax",
59 [RFKILL_TYPE_WWAN] = "wwan",
60 [RFKILL_TYPE_GPS] = "gps",
ac9455ef
TA
61 [RFKILL_TYPE_FM] = "fm",
62 [RFKILL_TYPE_NFC] = "nfc",
d35c1bb1 63};
3990f247 64
d35c1bb1 65DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int);
3990f247 66
d35c1bb1 67static int find_device(
d35c1bb1 68 const struct rfkill_event *event,
21384b81
YW
69 sd_device **ret) {
70 _cleanup_(sd_device_unrefp) sd_device *device = NULL;
d35c1bb1 71 _cleanup_free_ char *sysname = NULL;
d35c1bb1 72 const char *name;
21384b81 73 int r;
3990f247 74
d35c1bb1
LP
75 assert(event);
76 assert(ret);
3990f247 77
d35c1bb1
LP
78 if (asprintf(&sysname, "rfkill%i", event->idx) < 0)
79 return log_oom();
80
21384b81
YW
81 r = sd_device_new_from_subsystem_sysname(&device, "rfkill", sysname);
82 if (r < 0)
83 return log_full_errno(IN_SET(r, -ENOENT, -ENXIO, -ENODEV) ? LOG_DEBUG : LOG_ERR, r,
bc5e002e 84 "Failed to open device '%s': %m", sysname);
3990f247 85
21384b81
YW
86 r = sd_device_get_sysattr_value(device, "name", &name);
87 if (r < 0)
94ad3225 88 return log_device_debug_errno(device, r, "Device has no name, ignoring: %m");
4844262f 89
94ad3225 90 log_device_debug(device, "Operating on rfkill device '%s'.", name);
4844262f 91
21384b81 92 *ret = TAKE_PTR(device);
d35c1bb1
LP
93 return 0;
94}
95
d35c1bb1 96static int determine_state_file(
d35c1bb1 97 const struct rfkill_event *event,
d35c1bb1
LP
98 char **ret) {
99
21384b81 100 _cleanup_(sd_device_unrefp) sd_device *d = NULL, *device = NULL;
d35c1bb1
LP
101 const char *path_id, *type;
102 char *state_file;
103 int r;
104
105 assert(event);
d35c1bb1
LP
106 assert(ret);
107
21384b81 108 r = find_device(event, &d);
8e707663
BB
109 if (r < 0)
110 return r;
111
1b47436e 112 r = device_wait_for_initialization(d, "rfkill", USEC_INFINITY, &device);
d35c1bb1
LP
113 if (r < 0)
114 return r;
115
116 assert_se(type = rfkill_type_to_string(event->type));
3990f247 117
21384b81 118 if (sd_device_get_property_value(device, "ID_PATH", &path_id) >= 0) {
d35c1bb1
LP
119 _cleanup_free_ char *escaped_path_id = NULL;
120
f6f738db 121 escaped_path_id = cescape(path_id);
d35c1bb1
LP
122 if (!escaped_path_id)
123 return log_oom();
f6f738db 124
605405c6 125 state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type);
f6f738db 126 } else
605405c6 127 state_file = strjoin("/var/lib/systemd/rfkill/", type);
d35c1bb1
LP
128
129 if (!state_file)
130 return log_oom();
131
132 *ret = state_file;
133 return 0;
134}
135
5f5f0afc 136static int load_state(Context *c, const struct rfkill_event *event) {
d35c1bb1 137 _cleanup_free_ char *state_file = NULL, *value = NULL;
d35c1bb1 138 int b, r;
f6f738db 139
5f5f0afc
YW
140 assert(c);
141 assert(c->rfkill_fd >= 0);
d35c1bb1
LP
142 assert(event);
143
934ae16b 144 if (shall_restore_state() == 0)
d35c1bb1
LP
145 return 0;
146
21384b81 147 r = determine_state_file(event, &state_file);
d35c1bb1
LP
148 if (r < 0)
149 return r;
150
151 r = read_one_line_file(state_file, &value);
a2176045
TM
152 if (IN_SET(r, -ENOENT, 0)) {
153 /* No state file or it's truncated? Then save the current state */
d35c1bb1 154
e82e549f 155 r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755);
d35c1bb1
LP
156 if (r < 0)
157 return log_error_errno(r, "Failed to write state file %s: %m", state_file);
158
159 log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file);
160 return 0;
161 }
162 if (r < 0)
163 return log_error_errno(r, "Failed to read state file %s: %m", state_file);
164
165 b = parse_boolean(value);
166 if (b < 0)
167 return log_error_errno(b, "Failed to parse state file %s: %m", state_file);
168
6c7afdea 169 struct rfkill_event we = {
d35c1bb1 170 .idx = event->idx,
6c7afdea 171 .op = RFKILL_OP_CHANGE,
d35c1bb1
LP
172 .soft = b,
173 };
a71c0968
ZJS
174 assert_cc(offsetof(struct rfkill_event, op) < RFKILL_EVENT_SIZE_V1);
175 assert_cc(offsetof(struct rfkill_event, soft) < RFKILL_EVENT_SIZE_V1);
d35c1bb1 176
6c7afdea 177 ssize_t l = write(c->rfkill_fd, &we, sizeof we);
d35c1bb1
LP
178 if (l < 0)
179 return log_error_errno(errno, "Failed to restore rfkill state for %i: %m", event->idx);
ab1aa636 180 if ((size_t)l < RFKILL_EVENT_SIZE_V1) /* l cannot be < 0 here. Cast to fix -Werror=sign-compare */
baaa35ad 181 return log_error_errno(SYNTHETIC_ERRNO(EIO),
6c7afdea
ZJS
182 "Couldn't write rfkill event structure, too short (wrote %zd of %zu bytes).",
183 l, sizeof we);
a71c0968 184 log_debug("Writing struct rfkill_event successful (%zd of %zu bytes).", l, sizeof we);
d35c1bb1
LP
185
186 log_debug("Loaded state '%s' from %s.", one_zero(b), state_file);
187 return 0;
188}
189
5f5f0afc 190static void save_state_queue_remove(Context *c, int idx, const char *state_file) {
5f5f0afc
YW
191 assert(c);
192
80a226b2 193 LIST_FOREACH(queue, item, c->write_queue)
202cb8c3
BB
194 if ((state_file && streq(item->file, state_file)) || idx == item->rfkill_idx) {
195 log_debug("Canceled previous save state of '%s' to %s.", one_zero(item->state), item->file);
5f5f0afc 196 LIST_REMOVE(queue, c->write_queue, item);
202cb8c3
BB
197 write_queue_item_free(item);
198 }
202cb8c3
BB
199}
200
5f5f0afc 201static int save_state_queue(Context *c, const struct rfkill_event *event) {
d35c1bb1 202 _cleanup_free_ char *state_file = NULL;
202cb8c3 203 struct write_queue_item *item;
d35c1bb1
LP
204 int r;
205
5f5f0afc
YW
206 assert(c);
207 assert(c->rfkill_fd >= 0);
d35c1bb1
LP
208 assert(event);
209
21384b81 210 r = determine_state_file(event, &state_file);
d35c1bb1
LP
211 if (r < 0)
212 return r;
bc5e002e 213
5f5f0afc 214 save_state_queue_remove(c, event->idx, state_file);
d35c1bb1 215
202cb8c3
BB
216 item = new0(struct write_queue_item, 1);
217 if (!item)
218 return -ENOMEM;
219
1cc6c93a 220 item->file = TAKE_PTR(state_file);
202cb8c3
BB
221 item->rfkill_idx = event->idx;
222 item->state = event->soft;
202cb8c3 223
5f5f0afc 224 LIST_APPEND(queue, c->write_queue, item);
202cb8c3
BB
225
226 return 0;
227}
228
5f5f0afc 229static int save_state_cancel(Context *c, const struct rfkill_event *event) {
202cb8c3
BB
230 _cleanup_free_ char *state_file = NULL;
231 int r;
232
5f5f0afc
YW
233 assert(c);
234 assert(c->rfkill_fd >= 0);
202cb8c3
BB
235 assert(event);
236
21384b81 237 r = determine_state_file(event, &state_file);
5f5f0afc 238 save_state_queue_remove(c, event->idx, state_file);
d35c1bb1 239 if (r < 0)
202cb8c3 240 return r;
d35c1bb1 241
d35c1bb1
LP
242 return 0;
243}
244
5f5f0afc 245static int save_state_write_one(struct write_queue_item *item) {
202cb8c3
BB
246 int r;
247
e82e549f 248 r = write_string_file(item->file, one_zero(item->state), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755);
5f5f0afc
YW
249 if (r < 0)
250 return log_error_errno(r, "Failed to write state file %s: %m", item->file);
251
252 log_debug("Saved state '%s' to %s.", one_zero(item->state), item->file);
253 return 0;
254}
255
256static void context_save_and_clear(Context *c) {
257 struct write_queue_item *i;
258
259 assert(c);
260
261 while ((i = c->write_queue)) {
262 LIST_REMOVE(queue, c->write_queue, i);
263 (void) save_state_write_one(i);
264 write_queue_item_free(i);
202cb8c3 265 }
5f5f0afc
YW
266
267 safe_close(c->rfkill_fd);
202cb8c3
BB
268}
269
7a83c3ae 270static int run(int argc, char *argv[]) {
5f5f0afc 271 _cleanup_(context_save_and_clear) Context c = { .rfkill_fd = -1 };
d35c1bb1
LP
272 bool ready = false;
273 int r, n;
274
7a83c3ae
YW
275 if (argc > 1)
276 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires no arguments.");
3990f247 277
d2acb93d 278 log_setup();
3990f247 279
d35c1bb1 280 umask(0022);
354806fb 281
d35c1bb1 282 n = sd_listen_fds(false);
7a83c3ae
YW
283 if (n < 0)
284 return log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m");
285 if (n > 1)
286 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Got too many file descriptors.");
d35c1bb1
LP
287
288 if (n == 0) {
5f5f0afc
YW
289 c.rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
290 if (c.rfkill_fd < 0) {
d35c1bb1
LP
291 if (errno == ENOENT) {
292 log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting.");
7a83c3ae 293 return 0;
d35c1bb1
LP
294 }
295
7a83c3ae 296 return log_error_errno(errno, "Failed to open /dev/rfkill: %m");
3990f247 297 }
d35c1bb1 298 } else {
5f5f0afc 299 c.rfkill_fd = SD_LISTEN_FDS_START;
3990f247 300
5f5f0afc 301 r = fd_nonblock(c.rfkill_fd, 1);
7a83c3ae
YW
302 if (r < 0)
303 return log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m");
d35c1bb1
LP
304 }
305
306 for (;;) {
a71c0968 307 struct rfkill_event event = {};
3990f247 308
6c7afdea 309 ssize_t l = read(c.rfkill_fd, &event, sizeof event);
d35c1bb1 310 if (l < 0) {
6c7afdea
ZJS
311 if (errno != EAGAIN)
312 return log_error_errno(errno, "Failed to read from /dev/rfkill: %m");
313
314 if (!ready) {
315 /* Notify manager that we are now finished with processing whatever was
316 * queued */
4bf4f50f
ZJS
317 r = sd_notify(false, "READY=1");
318 if (r < 0)
319 log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
320
6c7afdea 321 ready = true;
d35c1bb1
LP
322 }
323
6c7afdea
ZJS
324 /* Hang around for a bit, maybe there's more coming */
325
326 r = fd_wait_for_event(c.rfkill_fd, POLLIN, EXIT_USEC);
327 if (r == -EINTR)
328 continue;
329 if (r < 0)
330 return log_error_errno(r, "Failed to poll() on device: %m");
331 if (r > 0)
332 continue;
333
334 log_debug("All events read and idle, exiting.");
335 break;
3990f247
LP
336 }
337
ab1aa636
LB
338 if ((size_t)l < RFKILL_EVENT_SIZE_V1) /* l cannot be < 0 here. Cast to fix -Werror=sign-compare */
339 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read of struct rfkill_event: (%zd < %zu)",
340 l, (size_t) RFKILL_EVENT_SIZE_V1); /* Casting necessary to make compiling with different kernel versions happy */
a71c0968
ZJS
341 log_debug("Reading struct rfkill_event: got %zd bytes.", l);
342
343 /* The event structure has more fields. We only care about the first few, so it's OK if we
344 * don't read the full structure. */
345 assert_cc(offsetof(struct rfkill_event, op) < RFKILL_EVENT_SIZE_V1);
346 assert_cc(offsetof(struct rfkill_event, type) < RFKILL_EVENT_SIZE_V1);
3990f247 347
6c7afdea 348 const char *type = rfkill_type_to_string(event.type);
d35c1bb1
LP
349 if (!type) {
350 log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type);
351 continue;
352 }
353
354 switch (event.op) {
355
356 case RFKILL_OP_ADD:
357 log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type);
5f5f0afc 358 (void) load_state(&c, &event);
d35c1bb1
LP
359 break;
360
361 case RFKILL_OP_DEL:
362 log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type);
5f5f0afc 363 (void) save_state_cancel(&c, &event);
d35c1bb1
LP
364 break;
365
366 case RFKILL_OP_CHANGE:
367 log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type);
5f5f0afc 368 (void) save_state_queue(&c, &event);
d35c1bb1
LP
369 break;
370
371 default:
372 log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type);
373 break;
374 }
3990f247
LP
375 }
376
7a83c3ae 377 return 0;
3990f247 378}
7a83c3ae
YW
379
380DEFINE_MAIN_FUNCTION(run);