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