]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/rfkill/rfkill.c
util-lib: move /proc/cmdline parsing code to proc-cmdline.[ch]
[thirdparty/systemd.git] / src / rfkill / rfkill.c
CommitLineData
3990f247
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2013 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
d35c1bb1
LP
22#include <linux/rfkill.h>
23#include <poll.h>
24
3990f247 25#include "libudev.h"
d35c1bb1
LP
26#include "sd-daemon.h"
27
4f5dd394 28#include "escape.h"
3ffd4af2 29#include "fd-util.h"
d35c1bb1 30#include "fileio.h"
c004493c 31#include "io-util.h"
d35c1bb1 32#include "mkdir.h"
6bedfcbb 33#include "parse-util.h"
4e731273 34#include "proc-cmdline.h"
8b43440b 35#include "string-table.h"
07630cea 36#include "string-util.h"
3990f247 37#include "udev-util.h"
d35c1bb1 38#include "util.h"
3990f247 39
d35c1bb1 40#define EXIT_USEC (5 * USEC_PER_SEC)
3990f247 41
d35c1bb1
LP
42static const char* const rfkill_type_table[NUM_RFKILL_TYPES] = {
43 [RFKILL_TYPE_ALL] = "all",
44 [RFKILL_TYPE_WLAN] = "wlan",
45 [RFKILL_TYPE_BLUETOOTH] = "bluetooth",
46 [RFKILL_TYPE_UWB] = "uwb",
47 [RFKILL_TYPE_WIMAX] = "wimax",
48 [RFKILL_TYPE_WWAN] = "wwan",
49 [RFKILL_TYPE_GPS] = "gps",
ac9455ef
TA
50 [RFKILL_TYPE_FM] = "fm",
51 [RFKILL_TYPE_NFC] = "nfc",
d35c1bb1 52};
3990f247 53
d35c1bb1 54DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int);
3990f247 55
d35c1bb1
LP
56static int find_device(
57 struct udev *udev,
58 const struct rfkill_event *event,
59 struct udev_device **ret) {
3990f247 60
d35c1bb1
LP
61 _cleanup_free_ char *sysname = NULL;
62 struct udev_device *device;
63 const char *name;
3990f247 64
d35c1bb1
LP
65 assert(udev);
66 assert(event);
67 assert(ret);
3990f247 68
d35c1bb1
LP
69 if (asprintf(&sysname, "rfkill%i", event->idx) < 0)
70 return log_oom();
71
72 device = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
73 if (!device)
74 return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m");
3990f247
LP
75
76 name = udev_device_get_sysattr_value(device, "name");
77 if (!name) {
d35c1bb1
LP
78 log_debug("Device has no name, ignoring.");
79 udev_device_unref(device);
80 return -ENOENT;
4844262f
LP
81 }
82
83 log_debug("Operating on rfkill device '%s'.", name);
84
d35c1bb1
LP
85 *ret = device;
86 return 0;
87}
88
89static int wait_for_initialized(
90 struct udev *udev,
91 struct udev_device *device,
92 struct udev_device **ret) {
93
94 _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL;
95 struct udev_device *d;
96 const char *sysname;
97 int watch_fd, r;
98
99 assert(udev);
100 assert(device);
101 assert(ret);
102
103 if (udev_device_get_is_initialized(device) != 0) {
104 *ret = udev_device_ref(device);
105 return 0;
3990f247
LP
106 }
107
d35c1bb1
LP
108 assert_se(sysname = udev_device_get_sysname(device));
109
110 /* Wait until the device is initialized, so that we can get
111 * access to the ID_PATH property */
112
113 monitor = udev_monitor_new_from_netlink(udev, "udev");
114 if (!monitor)
115 return log_error_errno(errno, "Failed to acquire monitor: %m");
116
117 r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "rfkill", NULL);
118 if (r < 0)
119 return log_error_errno(r, "Failed to add rfkill udev match to monitor: %m");
120
121 r = udev_monitor_enable_receiving(monitor);
122 if (r < 0)
123 return log_error_errno(r, "Failed to enable udev receiving: %m");
124
125 watch_fd = udev_monitor_get_fd(monitor);
126 if (watch_fd < 0)
127 return log_error_errno(watch_fd, "Failed to get watch fd: %m");
128
129 /* Check again, maybe things changed */
130 d = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
131 if (!d)
132 return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m");
133
134 if (udev_device_get_is_initialized(d) != 0) {
135 *ret = d;
136 return 0;
137 }
138
139 for (;;) {
140 _cleanup_udev_device_unref_ struct udev_device *t = NULL;
141
142 r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
143 if (r == -EINTR)
144 continue;
145 if (r < 0)
146 return log_error_errno(r, "Failed to watch udev monitor: %m");
147
148 t = udev_monitor_receive_device(monitor);
149 if (!t)
150 continue;
151
152 if (streq_ptr(udev_device_get_sysname(device), sysname)) {
153 *ret = udev_device_ref(t);
154 return 0;
155 }
3990f247 156 }
d35c1bb1
LP
157}
158
159static int determine_state_file(
160 struct udev *udev,
161 const struct rfkill_event *event,
162 struct udev_device *d,
163 char **ret) {
164
165 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
166 const char *path_id, *type;
167 char *state_file;
168 int r;
169
170 assert(event);
171 assert(d);
172 assert(ret);
173
174 r = wait_for_initialized(udev, d, &device);
175 if (r < 0)
176 return r;
177
178 assert_se(type = rfkill_type_to_string(event->type));
3990f247 179
f6f738db
LP
180 path_id = udev_device_get_property_value(device, "ID_PATH");
181 if (path_id) {
d35c1bb1
LP
182 _cleanup_free_ char *escaped_path_id = NULL;
183
f6f738db 184 escaped_path_id = cescape(path_id);
d35c1bb1
LP
185 if (!escaped_path_id)
186 return log_oom();
f6f738db 187
d35c1bb1 188 state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type, NULL);
f6f738db 189 } else
d35c1bb1
LP
190 state_file = strjoin("/var/lib/systemd/rfkill/", type, NULL);
191
192 if (!state_file)
193 return log_oom();
194
195 *ret = state_file;
196 return 0;
197}
198
199static int load_state(
200 int rfkill_fd,
201 struct udev *udev,
202 const struct rfkill_event *event) {
203
204 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
205 _cleanup_free_ char *state_file = NULL, *value = NULL;
206 struct rfkill_event we;
207 ssize_t l;
208 int b, r;
f6f738db 209
d35c1bb1
LP
210 assert(rfkill_fd >= 0);
211 assert(udev);
212 assert(event);
213
214 if (!shall_restore_state())
215 return 0;
216
217 r = find_device(udev, event, &device);
218 if (r < 0)
219 return r;
220
221 r = determine_state_file(udev, event, device, &state_file);
222 if (r < 0)
223 return r;
224
225 r = read_one_line_file(state_file, &value);
226 if (r == -ENOENT) {
227 /* No state file? Then save the current state */
228
229 r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
230 if (r < 0)
231 return log_error_errno(r, "Failed to write state file %s: %m", state_file);
232
233 log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file);
234 return 0;
235 }
236 if (r < 0)
237 return log_error_errno(r, "Failed to read state file %s: %m", state_file);
238
239 b = parse_boolean(value);
240 if (b < 0)
241 return log_error_errno(b, "Failed to parse state file %s: %m", state_file);
242
243 we = (struct rfkill_event) {
244 .op = RFKILL_OP_CHANGE,
245 .idx = event->idx,
246 .soft = b,
247 };
248
249 l = write(rfkill_fd, &we, sizeof(we));
250 if (l < 0)
251 return log_error_errno(errno, "Failed to restore rfkill state for %i: %m", event->idx);
252 if (l != sizeof(we)) {
253 log_error("Couldn't write rfkill event structure, too short.");
254 return -EIO;
255 }
256
257 log_debug("Loaded state '%s' from %s.", one_zero(b), state_file);
258 return 0;
259}
260
261static int save_state(
262 int rfkill_fd,
263 struct udev *udev,
264 const struct rfkill_event *event) {
265
266 _cleanup_udev_device_unref_ struct udev_device *device = NULL;
267 _cleanup_free_ char *state_file = NULL;
268 int r;
269
270 assert(rfkill_fd >= 0);
271 assert(udev);
272 assert(event);
273
274 r = find_device(udev, event, &device);
275 if (r < 0)
276 return r;
277
278 r = determine_state_file(udev, event, device, &state_file);
279 if (r < 0)
280 return r;
281
282 r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
283 if (r < 0)
284 return log_error_errno(r, "Failed to write state file %s: %m", state_file);
285
286 log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file);
287 return 0;
288}
289
290int main(int argc, char *argv[]) {
291 _cleanup_udev_unref_ struct udev *udev = NULL;
292 _cleanup_close_ int rfkill_fd = -1;
293 bool ready = false;
294 int r, n;
295
296 if (argc > 1) {
297 log_error("This program requires no arguments.");
3990f247
LP
298 return EXIT_FAILURE;
299 }
300
d35c1bb1
LP
301 log_set_target(LOG_TARGET_AUTO);
302 log_parse_environment();
303 log_open();
3990f247 304
d35c1bb1 305 umask(0022);
354806fb 306
d35c1bb1
LP
307 udev = udev_new();
308 if (!udev) {
309 r = log_oom();
310 goto finish;
311 }
312
313 r = mkdir_p("/var/lib/systemd/rfkill", 0755);
314 if (r < 0) {
315 log_error_errno(r, "Failed to create rfkill directory: %m");
316 goto finish;
317 }
318
319 n = sd_listen_fds(false);
320 if (n < 0) {
321 r = log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m");
322 goto finish;
323 }
324 if (n > 1) {
325 log_error("Got too many file descriptors.");
326 r = -EINVAL;
327 goto finish;
328 }
329
330 if (n == 0) {
331 rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
332 if (rfkill_fd < 0) {
333 if (errno == ENOENT) {
334 log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting.");
335 r = 0;
336 goto finish;
337 }
338
339 r = log_error_errno(errno, "Failed to open /dev/rfkill: %m");
340 goto finish;
3990f247 341 }
d35c1bb1
LP
342 } else {
343 rfkill_fd = SD_LISTEN_FDS_START;
3990f247 344
d35c1bb1 345 r = fd_nonblock(rfkill_fd, 1);
3990f247 346 if (r < 0) {
d35c1bb1
LP
347 log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m");
348 goto finish;
3990f247 349 }
d35c1bb1
LP
350 }
351
352 for (;;) {
353 struct rfkill_event event;
354 const char *type;
355 ssize_t l;
3990f247 356
d35c1bb1
LP
357 l = read(rfkill_fd, &event, sizeof(event));
358 if (l < 0) {
359 if (errno == EAGAIN) {
3990f247 360
d35c1bb1
LP
361 if (!ready) {
362 /* Notify manager that we are
363 * now finished with
364 * processing whatever was
365 * queued */
366 (void) sd_notify(false, "READY=1");
367 ready = true;
368 }
369
370 /* Hang around for a bit, maybe there's more coming */
371
372 r = fd_wait_for_event(rfkill_fd, POLLIN, EXIT_USEC);
373 if (r == -EINTR)
374 continue;
375 if (r < 0) {
376 log_error_errno(r, "Failed to poll() on device: %m");
377 goto finish;
378 }
379 if (r > 0)
380 continue;
381
382 log_debug("All events read and idle, exiting.");
383 break;
384 }
385
386 log_error_errno(errno, "Failed to read from /dev/rfkill: %m");
3990f247
LP
387 }
388
d35c1bb1
LP
389 if (l != RFKILL_EVENT_SIZE_V1) {
390 log_error("Read event structure of invalid size.");
391 r = -EIO;
392 goto finish;
3990f247
LP
393 }
394
d35c1bb1
LP
395 type = rfkill_type_to_string(event.type);
396 if (!type) {
397 log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type);
398 continue;
399 }
400
401 switch (event.op) {
402
403 case RFKILL_OP_ADD:
404 log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type);
405 (void) load_state(rfkill_fd, udev, &event);
406 break;
407
408 case RFKILL_OP_DEL:
409 log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type);
410 break;
411
412 case RFKILL_OP_CHANGE:
413 log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type);
414 (void) save_state(rfkill_fd, udev, &event);
415 break;
416
417 default:
418 log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type);
419 break;
420 }
3990f247
LP
421 }
422
d35c1bb1
LP
423 r = 0;
424
425finish:
426 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
3990f247 427}