]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
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" |
7a83c3ae | 19 | #include "main-func.h" |
d35c1bb1 | 20 | #include "mkdir.h" |
6bedfcbb | 21 | #include "parse-util.h" |
4e731273 | 22 | #include "proc-cmdline.h" |
8b43440b | 23 | #include "string-table.h" |
07630cea | 24 | #include "string-util.h" |
ed435031 | 25 | #include "udev-util.h" |
d35c1bb1 | 26 | #include "util.h" |
202cb8c3 | 27 | #include "list.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 | ||
ed435031 | 112 | r = device_wait_for_initialization(d, "rfkill", &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 LP |
137 | _cleanup_free_ char *state_file = NULL, *value = NULL; |
138 | struct rfkill_event we; | |
139 | ssize_t l; | |
140 | int b, r; | |
f6f738db | 141 | |
5f5f0afc YW |
142 | assert(c); |
143 | assert(c->rfkill_fd >= 0); | |
d35c1bb1 LP |
144 | assert(event); |
145 | ||
934ae16b | 146 | if (shall_restore_state() == 0) |
d35c1bb1 LP |
147 | return 0; |
148 | ||
21384b81 | 149 | r = determine_state_file(event, &state_file); |
d35c1bb1 LP |
150 | if (r < 0) |
151 | return r; | |
152 | ||
153 | r = read_one_line_file(state_file, &value); | |
a2176045 TM |
154 | if (IN_SET(r, -ENOENT, 0)) { |
155 | /* No state file or it's truncated? Then save the current state */ | |
d35c1bb1 LP |
156 | |
157 | r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); | |
158 | if (r < 0) | |
159 | return log_error_errno(r, "Failed to write state file %s: %m", state_file); | |
160 | ||
161 | log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); | |
162 | return 0; | |
163 | } | |
164 | if (r < 0) | |
165 | return log_error_errno(r, "Failed to read state file %s: %m", state_file); | |
166 | ||
167 | b = parse_boolean(value); | |
168 | if (b < 0) | |
169 | return log_error_errno(b, "Failed to parse state file %s: %m", state_file); | |
170 | ||
171 | we = (struct rfkill_event) { | |
172 | .op = RFKILL_OP_CHANGE, | |
173 | .idx = event->idx, | |
174 | .soft = b, | |
175 | }; | |
176 | ||
5f5f0afc | 177 | 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); | |
baaa35ad ZJS |
180 | if (l != sizeof(we)) |
181 | return log_error_errno(SYNTHETIC_ERRNO(EIO), | |
182 | "Couldn't write rfkill event structure, too short."); | |
d35c1bb1 LP |
183 | |
184 | log_debug("Loaded state '%s' from %s.", one_zero(b), state_file); | |
185 | return 0; | |
186 | } | |
187 | ||
5f5f0afc | 188 | static void save_state_queue_remove(Context *c, int idx, const char *state_file) { |
202cb8c3 BB |
189 | struct write_queue_item *item, *tmp; |
190 | ||
5f5f0afc YW |
191 | assert(c); |
192 | ||
193 | LIST_FOREACH_SAFE(queue, item, tmp, 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 | } | |
199 | } | |
200 | } | |
201 | ||
5f5f0afc | 202 | static int save_state_queue(Context *c, const struct rfkill_event *event) { |
d35c1bb1 | 203 | _cleanup_free_ char *state_file = NULL; |
202cb8c3 | 204 | struct write_queue_item *item; |
d35c1bb1 LP |
205 | int r; |
206 | ||
5f5f0afc YW |
207 | assert(c); |
208 | assert(c->rfkill_fd >= 0); | |
d35c1bb1 LP |
209 | assert(event); |
210 | ||
21384b81 | 211 | r = determine_state_file(event, &state_file); |
d35c1bb1 LP |
212 | if (r < 0) |
213 | return r; | |
bc5e002e | 214 | |
5f5f0afc | 215 | save_state_queue_remove(c, event->idx, state_file); |
d35c1bb1 | 216 | |
202cb8c3 BB |
217 | item = new0(struct write_queue_item, 1); |
218 | if (!item) | |
219 | return -ENOMEM; | |
220 | ||
1cc6c93a | 221 | item->file = TAKE_PTR(state_file); |
202cb8c3 BB |
222 | item->rfkill_idx = event->idx; |
223 | item->state = event->soft; | |
202cb8c3 | 224 | |
5f5f0afc | 225 | LIST_APPEND(queue, c->write_queue, item); |
202cb8c3 BB |
226 | |
227 | return 0; | |
228 | } | |
229 | ||
5f5f0afc | 230 | static int save_state_cancel(Context *c, const struct rfkill_event *event) { |
202cb8c3 BB |
231 | _cleanup_free_ char *state_file = NULL; |
232 | int r; | |
233 | ||
5f5f0afc YW |
234 | assert(c); |
235 | assert(c->rfkill_fd >= 0); | |
202cb8c3 BB |
236 | assert(event); |
237 | ||
21384b81 | 238 | r = determine_state_file(event, &state_file); |
5f5f0afc | 239 | save_state_queue_remove(c, event->idx, state_file); |
d35c1bb1 | 240 | if (r < 0) |
202cb8c3 | 241 | return r; |
d35c1bb1 | 242 | |
d35c1bb1 LP |
243 | return 0; |
244 | } | |
245 | ||
5f5f0afc | 246 | static int save_state_write_one(struct write_queue_item *item) { |
202cb8c3 BB |
247 | int r; |
248 | ||
5f5f0afc YW |
249 | r = write_string_file(item->file, one_zero(item->state), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); |
250 | if (r < 0) | |
251 | return log_error_errno(r, "Failed to write state file %s: %m", item->file); | |
252 | ||
253 | log_debug("Saved state '%s' to %s.", one_zero(item->state), item->file); | |
254 | return 0; | |
255 | } | |
256 | ||
257 | static void context_save_and_clear(Context *c) { | |
258 | struct write_queue_item *i; | |
259 | ||
260 | assert(c); | |
261 | ||
262 | while ((i = c->write_queue)) { | |
263 | LIST_REMOVE(queue, c->write_queue, i); | |
264 | (void) save_state_write_one(i); | |
265 | write_queue_item_free(i); | |
202cb8c3 | 266 | } |
5f5f0afc YW |
267 | |
268 | safe_close(c->rfkill_fd); | |
202cb8c3 BB |
269 | } |
270 | ||
7a83c3ae | 271 | static int run(int argc, char *argv[]) { |
5f5f0afc | 272 | _cleanup_(context_save_and_clear) Context c = { .rfkill_fd = -1 }; |
d35c1bb1 LP |
273 | bool ready = false; |
274 | int r, n; | |
275 | ||
7a83c3ae YW |
276 | if (argc > 1) |
277 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires no arguments."); | |
3990f247 | 278 | |
6bf3c61c | 279 | log_setup_service(); |
3990f247 | 280 | |
d35c1bb1 | 281 | umask(0022); |
354806fb | 282 | |
d35c1bb1 | 283 | r = mkdir_p("/var/lib/systemd/rfkill", 0755); |
7a83c3ae YW |
284 | if (r < 0) |
285 | return log_error_errno(r, "Failed to create rfkill directory: %m"); | |
d35c1bb1 LP |
286 | |
287 | n = sd_listen_fds(false); | |
7a83c3ae YW |
288 | if (n < 0) |
289 | return log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m"); | |
290 | if (n > 1) | |
291 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Got too many file descriptors."); | |
d35c1bb1 LP |
292 | |
293 | if (n == 0) { | |
5f5f0afc YW |
294 | c.rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); |
295 | if (c.rfkill_fd < 0) { | |
d35c1bb1 LP |
296 | if (errno == ENOENT) { |
297 | log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting."); | |
7a83c3ae | 298 | return 0; |
d35c1bb1 LP |
299 | } |
300 | ||
7a83c3ae | 301 | return log_error_errno(errno, "Failed to open /dev/rfkill: %m"); |
3990f247 | 302 | } |
d35c1bb1 | 303 | } else { |
5f5f0afc | 304 | c.rfkill_fd = SD_LISTEN_FDS_START; |
3990f247 | 305 | |
5f5f0afc | 306 | r = fd_nonblock(c.rfkill_fd, 1); |
7a83c3ae YW |
307 | if (r < 0) |
308 | return log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m"); | |
d35c1bb1 LP |
309 | } |
310 | ||
311 | for (;;) { | |
312 | struct rfkill_event event; | |
313 | const char *type; | |
314 | ssize_t l; | |
3990f247 | 315 | |
5f5f0afc | 316 | l = read(c.rfkill_fd, &event, sizeof(event)); |
d35c1bb1 LP |
317 | if (l < 0) { |
318 | if (errno == EAGAIN) { | |
3990f247 | 319 | |
d35c1bb1 LP |
320 | if (!ready) { |
321 | /* Notify manager that we are | |
322 | * now finished with | |
323 | * processing whatever was | |
324 | * queued */ | |
325 | (void) sd_notify(false, "READY=1"); | |
326 | ready = true; | |
327 | } | |
328 | ||
329 | /* Hang around for a bit, maybe there's more coming */ | |
330 | ||
5f5f0afc | 331 | r = fd_wait_for_event(c.rfkill_fd, POLLIN, EXIT_USEC); |
d35c1bb1 LP |
332 | if (r == -EINTR) |
333 | continue; | |
7a83c3ae YW |
334 | if (r < 0) |
335 | return log_error_errno(r, "Failed to poll() on device: %m"); | |
d35c1bb1 LP |
336 | if (r > 0) |
337 | continue; | |
338 | ||
339 | log_debug("All events read and idle, exiting."); | |
340 | break; | |
341 | } | |
342 | ||
343 | log_error_errno(errno, "Failed to read from /dev/rfkill: %m"); | |
3990f247 LP |
344 | } |
345 | ||
7a83c3ae YW |
346 | if (l != RFKILL_EVENT_SIZE_V1) |
347 | return log_error_errno(SYNTHETIC_ERRNO(EIO), "Read event structure of invalid size."); | |
3990f247 | 348 | |
d35c1bb1 LP |
349 | type = rfkill_type_to_string(event.type); |
350 | if (!type) { | |
351 | log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type); | |
352 | continue; | |
353 | } | |
354 | ||
355 | switch (event.op) { | |
356 | ||
357 | case RFKILL_OP_ADD: | |
358 | log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type); | |
5f5f0afc | 359 | (void) load_state(&c, &event); |
d35c1bb1 LP |
360 | break; |
361 | ||
362 | case RFKILL_OP_DEL: | |
363 | log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type); | |
5f5f0afc | 364 | (void) save_state_cancel(&c, &event); |
d35c1bb1 LP |
365 | break; |
366 | ||
367 | case RFKILL_OP_CHANGE: | |
368 | log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type); | |
5f5f0afc | 369 | (void) save_state_queue(&c, &event); |
d35c1bb1 LP |
370 | break; |
371 | ||
372 | default: | |
373 | log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type); | |
374 | break; | |
375 | } | |
3990f247 LP |
376 | } |
377 | ||
7a83c3ae | 378 | return 0; |
3990f247 | 379 | } |
7a83c3ae YW |
380 | |
381 | DEFINE_MAIN_FUNCTION(run); |