]>
Commit | Line | Data |
---|---|---|
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 |
33 | typedef 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 |
40 | typedef struct Context { |
41 | LIST_HEAD(write_queue_item, write_queue); | |
42 | int rfkill_fd; | |
43 | } Context; | |
44 | ||
3b3d1737 LP |
45 | static 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 |
53 | static 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 | 65 | DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int); |
3990f247 | 66 | |
d35c1bb1 | 67 | static 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 | 96 | static 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 | 136 | static 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 | 190 | static 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 | 201 | static 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 | 229 | static 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 | 245 | static 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 | ||
256 | static 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 | 270 | static 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 | |
380 | DEFINE_MAIN_FUNCTION(run); |