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