]>
Commit | Line | Data |
---|---|---|
d5fad6eb | 1 | /* |
9abd5e4b KZ |
2 | * SPDX-License-Identifier: ISC |
3 | * | |
e69263c0 SK |
4 | * /dev/rfkill userspace tool |
5 | * | |
6 | * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> | |
7 | * Copyright 2009 Marcel Holtmann <marcel@holtmann.org> | |
8 | * Copyright 2009 Tim Gardner <tim.gardner@canonical.com> | |
f4949fcc | 9 | * Copyright 2017 Sami Kerola <kerolasa@iki.fi> |
7d2a9960 | 10 | * Copyright (C) 2017 Karel Zak <kzak@redhat.com> |
e69263c0 SK |
11 | * |
12 | * Permission to use, copy, modify, and/or distribute this software for any | |
13 | * purpose with or without fee is hereby granted, provided that the above | |
14 | * copyright notice and this permission notice appear in all copies. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
17 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
18 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
19 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
20 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
21 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
22 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
d5fad6eb | 23 | */ |
b1849b89 | 24 | #include <ctype.h> |
367f4080 | 25 | #include <getopt.h> |
b3849c66 | 26 | #include <libsmartcols.h> |
367f4080 | 27 | #include <linux/rfkill.h> |
74ccba3e | 28 | #include <poll.h> |
fd3d4ec7 | 29 | #include <sys/syslog.h> |
6217bea3 | 30 | #include <sys/time.h> |
d5fad6eb | 31 | |
c0d78c90 SK |
32 | #include "c.h" |
33 | #include "closestream.h" | |
34 | #include "nls.h" | |
b3849c66 | 35 | #include "optutils.h" |
367f4080 SK |
36 | #include "pathnames.h" |
37 | #include "strutils.h" | |
7d0b3acb | 38 | #include "timeutils.h" |
367f4080 | 39 | #include "widechar.h" |
b3849c66 | 40 | #include "xalloc.h" |
20fae1bd | 41 | #include "all-io.h" |
367f4080 | 42 | |
ea05c23a KZ |
43 | |
44 | /* | |
45 | * NFC supported by kernel since v3.10 (year 2013); FM and another types are from | |
46 | * year 2009 (2.6.33) or older. | |
47 | */ | |
48 | #ifndef RFKILL_TYPE_NFC | |
40defd0c RM |
49 | # ifndef RFKILL_TYPE_FM |
50 | # define RFKILL_TYPE_FM RFKILL_TYPE_GPS + 1 | |
51 | # endif | |
ea05c23a | 52 | # define RFKILL_TYPE_NFC RFKILL_TYPE_FM + 1 |
40defd0c RM |
53 | # undef NUM_RFKILL_TYPES |
54 | # define NUM_RFKILL_TYPES RFKILL_TYPE_NFC + 1 | |
ea05c23a KZ |
55 | #endif |
56 | ||
367f4080 | 57 | struct rfkill_type_str { |
7d2a9960 KZ |
58 | enum rfkill_type type; /* ID */ |
59 | const char *name; /* generic name */ | |
60 | const char *desc; /* human readable name */ | |
367f4080 SK |
61 | }; |
62 | ||
63 | static const struct rfkill_type_str rfkill_type_strings[] = { | |
64 | { .type = RFKILL_TYPE_ALL, .name = "all" }, | |
7d2a9960 KZ |
65 | { .type = RFKILL_TYPE_WLAN, .name = "wlan", .desc = "Wireless LAN" }, |
66 | { .type = RFKILL_TYPE_WLAN, .name = "wifi" }, /* alias */ | |
67 | { .type = RFKILL_TYPE_BLUETOOTH, .name = "bluetooth", .desc = "Bluetooth" }, | |
68 | { .type = RFKILL_TYPE_UWB, .name = "uwb", .desc = "Ultra-Wideband" }, | |
367f4080 | 69 | { .type = RFKILL_TYPE_UWB, .name = "ultrawideband" }, /* alias */ |
7d2a9960 KZ |
70 | { .type = RFKILL_TYPE_WIMAX, .name = "wimax", .desc = "WiMAX" }, |
71 | { .type = RFKILL_TYPE_WWAN, .name = "wwan", .desc = "Wireless WAN" }, | |
9e33cddd | 72 | { .type = RFKILL_TYPE_GPS, .name = "gps", .desc = "GPS" }, |
7d2a9960 KZ |
73 | { .type = RFKILL_TYPE_FM, .name = "fm", .desc = "FM" }, |
74 | { .type = RFKILL_TYPE_NFC, .name = "nfc", .desc = "NFC" }, | |
367f4080 SK |
75 | { .type = NUM_RFKILL_TYPES, .name = NULL } |
76 | }; | |
77 | ||
78 | struct rfkill_id { | |
79 | union { | |
80 | enum rfkill_type type; | |
81 | uint32_t index; | |
82 | }; | |
83 | enum { | |
84 | RFKILL_IS_INVALID, | |
85 | RFKILL_IS_TYPE, | |
86 | RFKILL_IS_INDEX, | |
b03378e7 | 87 | RFKILL_IS_ALL |
367f4080 SK |
88 | } result; |
89 | }; | |
d5fad6eb | 90 | |
18d3a03f KZ |
91 | /* supported actions */ |
92 | enum { | |
93 | ACT_LIST, | |
94 | ACT_HELP, | |
95 | ACT_EVENT, | |
96 | ACT_BLOCK, | |
7d2a9960 | 97 | ACT_UNBLOCK, |
20fae1bd | 98 | ACT_TOGGLE, |
7d2a9960 KZ |
99 | |
100 | ACT_LIST_OLD | |
18d3a03f KZ |
101 | }; |
102 | ||
103 | static char *rfkill_actions[] = { | |
104 | [ACT_LIST] = "list", | |
105 | [ACT_HELP] = "help", | |
106 | [ACT_EVENT] = "event", | |
107 | [ACT_BLOCK] = "block", | |
20fae1bd KZ |
108 | [ACT_UNBLOCK] = "unblock", |
109 | [ACT_TOGGLE] = "toggle" | |
18d3a03f KZ |
110 | }; |
111 | ||
b3849c66 SK |
112 | /* column IDs */ |
113 | enum { | |
114 | COL_DEVICE, | |
115 | COL_ID, | |
116 | COL_TYPE, | |
7d2a9960 | 117 | COL_DESC, |
b3849c66 SK |
118 | COL_SOFT, |
119 | COL_HARD | |
120 | }; | |
121 | ||
122 | /* column names */ | |
123 | struct colinfo { | |
124 | const char *name; /* header */ | |
125 | double whint; /* width hint (N < 1 is in percent of termwidth) */ | |
126 | int flags; /* SCOLS_FL_* */ | |
127 | const char *help; | |
128 | }; | |
129 | ||
130 | /* columns descriptions */ | |
131 | static const struct colinfo infos[] = { | |
132 | [COL_DEVICE] = {"DEVICE", 0, 0, N_("kernel device name")}, | |
7d8e3bcd | 133 | [COL_ID] = {"ID", 2, SCOLS_FL_RIGHT, N_("device identifier value")}, |
b3849c66 | 134 | [COL_TYPE] = {"TYPE", 0, 0, N_("device type name that can be used as identifier")}, |
7d2a9960 | 135 | [COL_DESC] = {"TYPE-DESC", 0, 0, N_("device type description")}, |
7d8e3bcd KZ |
136 | [COL_SOFT] = {"SOFT", 0, SCOLS_FL_RIGHT, N_("status of software block")}, |
137 | [COL_HARD] = {"HARD", 0, SCOLS_FL_RIGHT, N_("status of hardware block")} | |
b3849c66 SK |
138 | }; |
139 | ||
140 | static int columns[ARRAY_SIZE(infos) * 2]; | |
141 | static size_t ncolumns; | |
142 | ||
143 | struct control { | |
4f892d77 | 144 | struct libscols_table *tb; |
b3849c66 SK |
145 | unsigned int |
146 | json:1, | |
147 | no_headings:1, | |
148 | raw:1; | |
149 | }; | |
150 | ||
151 | static int column_name_to_id(const char *name, size_t namesz) | |
152 | { | |
153 | size_t i; | |
154 | ||
155 | assert(name); | |
156 | ||
157 | for (i = 0; i < ARRAY_SIZE(infos); i++) { | |
158 | const char *cn = infos[i].name; | |
159 | ||
160 | if (!strncasecmp(name, cn, namesz) && !*(cn + namesz)) | |
161 | return i; | |
162 | } | |
163 | warnx(_("unknown column: %s"), name); | |
164 | return -1; | |
165 | } | |
166 | ||
167 | static int get_column_id(size_t num) | |
168 | { | |
169 | assert(num < ncolumns); | |
170 | assert(columns[num] < (int)ARRAY_SIZE(infos)); | |
171 | return columns[num]; | |
172 | } | |
173 | ||
174 | static const struct colinfo *get_column_info(int num) | |
175 | { | |
176 | return &infos[get_column_id(num)]; | |
177 | } | |
178 | ||
18d3a03f KZ |
179 | static int string_to_action(const char *str) |
180 | { | |
181 | size_t i; | |
182 | ||
183 | for (i = 0; i < ARRAY_SIZE(rfkill_actions); i++) | |
184 | if (strcmp(str, rfkill_actions[i]) == 0) | |
185 | return i; | |
186 | ||
187 | return -EINVAL; | |
188 | } | |
189 | ||
20fae1bd | 190 | static int rfkill_open(int rdonly, int nonblock) |
49f4f7b6 KZ |
191 | { |
192 | int fd; | |
193 | ||
20fae1bd | 194 | fd = open(_PATH_DEV_RFKILL, rdonly ? O_RDONLY : O_RDWR); |
49f4f7b6 KZ |
195 | if (fd < 0) { |
196 | warn(_("cannot open %s"), _PATH_DEV_RFKILL); | |
197 | return -errno; | |
198 | } | |
199 | ||
200 | if (nonblock && fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { | |
201 | warn(_("cannot set non-blocking %s"), _PATH_DEV_RFKILL); | |
202 | close(fd); | |
203 | return -errno; | |
204 | } | |
205 | ||
206 | return fd; | |
207 | } | |
208 | ||
209 | /* returns: 0 success, 1 read again, < 0 error */ | |
210 | static int rfkill_read_event(int fd, struct rfkill_event *event) | |
211 | { | |
dec4fae2 | 212 | ssize_t len = read(fd, event, sizeof(*event)); |
49f4f7b6 KZ |
213 | |
214 | if (len < 0) { | |
215 | if (errno == EAGAIN) | |
216 | return 1; | |
217 | warn(_("cannot read %s"), _PATH_DEV_RFKILL); | |
218 | return -errno; | |
219 | } | |
220 | ||
185bc3a8 | 221 | if ((size_t) len < (size_t) RFKILL_EVENT_SIZE_V1) { |
f798aed1 | 222 | warnx(_("wrong size of rfkill event: %zu < %zu"), |
185bc3a8 | 223 | (size_t) len, (size_t) RFKILL_EVENT_SIZE_V1); |
49f4f7b6 KZ |
224 | return 1; |
225 | } | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | ||
367f4080 | 231 | static int rfkill_event(void) |
07d517c0 | 232 | { |
91724a71 MR |
233 | enum { |
234 | POLLFD_RFKILL, | |
235 | POLLFD_STDOUT, | |
236 | POLLFD_COUNT | |
237 | }; | |
51da1093 | 238 | struct rfkill_event event; |
6217bea3 | 239 | struct timeval tv; |
4111bb3a | 240 | char date_buf[ISO_BUFSIZ]; |
91724a71 | 241 | struct pollfd p[POLLFD_COUNT]; |
3c5022c6 | 242 | int fd, n; |
07d517c0 | 243 | |
20fae1bd | 244 | fd = rfkill_open(1, 0); |
49f4f7b6 | 245 | if (fd < 0) |
20fae1bd | 246 | return fd; |
07d517c0 MH |
247 | |
248 | memset(&p, 0, sizeof(p)); | |
91724a71 MR |
249 | p[POLLFD_RFKILL].fd = fd; |
250 | p[POLLFD_RFKILL].events = POLLIN | POLLHUP; | |
251 | p[POLLFD_STDOUT].fd = STDOUT_FILENO; | |
252 | p[POLLFD_STDOUT].events = 0; | |
07d517c0 | 253 | |
3c5022c6 | 254 | /* interrupted by signal only */ |
07d517c0 | 255 | while (1) { |
49f4f7b6 KZ |
256 | int rc = 1; /* recover-able error */ |
257 | ||
91724a71 | 258 | n = poll(p, ARRAY_SIZE(p), -1); |
07d517c0 | 259 | if (n < 0) { |
367f4080 | 260 | warn(_("failed to poll %s"), _PATH_DEV_RFKILL); |
3c5022c6 | 261 | goto failed; |
07d517c0 MH |
262 | } |
263 | ||
91724a71 MR |
264 | if (p[POLLFD_STDOUT].revents) |
265 | goto failed; /* read end of stdout closed */ | |
266 | if (p[POLLFD_RFKILL].revents) | |
49f4f7b6 KZ |
267 | rc = rfkill_read_event(fd, &event); |
268 | if (rc < 0) | |
3c5022c6 | 269 | goto failed; |
49f4f7b6 | 270 | if (rc) |
07d517c0 | 271 | continue; |
49f4f7b6 | 272 | |
6217bea3 | 273 | gettimeofday(&tv, NULL); |
4111bb3a WP |
274 | strtimeval_iso(&tv, ISO_TIMESTAMP_COMMA, date_buf, |
275 | sizeof(date_buf)); | |
7d0b3acb SK |
276 | printf("%s: idx %u type %u op %u soft %u hard %u\n", |
277 | date_buf, | |
278 | event.idx, event.type, event.op, event.soft, event.hard); | |
1634aef2 | 279 | fflush(stdout); |
07d517c0 MH |
280 | } |
281 | ||
3c5022c6 | 282 | failed: |
07d517c0 | 283 | close(fd); |
3c5022c6 | 284 | return -1; |
07d517c0 MH |
285 | } |
286 | ||
7d2a9960 | 287 | static const char *get_sys_attr(uint32_t idx, const char *attr) |
07d517c0 | 288 | { |
7d2a9960 | 289 | static char name[128]; |
f99148d6 KZ |
290 | char path[PATH_MAX]; |
291 | FILE *f; | |
7d2a9960 KZ |
292 | char *p; |
293 | ||
f99148d6 KZ |
294 | snprintf(path, sizeof(path), _PATH_SYS_RFKILL "/rfkill%u/%s", idx, attr); |
295 | f = fopen(path, "r"); | |
7d2a9960 KZ |
296 | if (!f) |
297 | goto done; | |
298 | if (!fgets(name, sizeof(name), f)) | |
299 | goto done; | |
300 | p = strchr(name, '\n'); | |
301 | if (p) | |
302 | *p = '\0'; | |
303 | done: | |
304 | if (f) | |
305 | fclose(f); | |
07d517c0 MH |
306 | return name; |
307 | } | |
308 | ||
fafd9121 DS |
309 | static struct rfkill_id rfkill_id_to_type(const char *s) |
310 | { | |
311 | const struct rfkill_type_str *p; | |
061e26d0 | 312 | struct rfkill_id ret = { .result = 0 }; |
fafd9121 DS |
313 | |
314 | if (islower(*s)) { | |
315 | for (p = rfkill_type_strings; p->name != NULL; p++) { | |
367f4080 | 316 | if (!strcmp(s, p->name)) { |
fafd9121 | 317 | ret.type = p->type; |
b03378e7 SK |
318 | if (!strcmp(s, "all")) |
319 | ret.result = RFKILL_IS_ALL; | |
320 | else | |
321 | ret.result = RFKILL_IS_TYPE; | |
fafd9121 DS |
322 | return ret; |
323 | } | |
324 | } | |
325 | } else if (isdigit(*s)) { | |
326 | /* assume a numeric character implies an index. */ | |
f806a238 SK |
327 | char filename[64]; |
328 | ||
367f4080 | 329 | ret.index = strtou32_or_err(s, _("invalid identifier")); |
f806a238 SK |
330 | snprintf(filename, sizeof(filename) - 1, |
331 | _PATH_SYS_RFKILL "/rfkill%" PRIu32 "/name", ret.index); | |
332 | if (access(filename, F_OK) == 0) | |
333 | ret.result = RFKILL_IS_INDEX; | |
334 | else | |
335 | ret.result = RFKILL_IS_INVALID; | |
fafd9121 DS |
336 | return ret; |
337 | } | |
338 | ||
339 | ret.result = RFKILL_IS_INVALID; | |
340 | return ret; | |
341 | } | |
342 | ||
7d2a9960 KZ |
343 | static const char *rfkill_type_to_desc(enum rfkill_type type) |
344 | { | |
345 | size_t i; | |
346 | ||
347 | for (i = 0; i < ARRAY_SIZE(rfkill_type_strings); i++) { | |
348 | if (type == rfkill_type_strings[i].type) | |
349 | return rfkill_type_strings[i].desc; | |
350 | } | |
351 | ||
352 | return NULL; | |
353 | } | |
354 | ||
355 | ||
49f4f7b6 KZ |
356 | static int event_match(struct rfkill_event *event, struct rfkill_id *id) |
357 | { | |
358 | if (event->op != RFKILL_OP_ADD) | |
359 | return 0; | |
360 | ||
361 | /* filter out unwanted results */ | |
362 | switch (id->result) { | |
363 | case RFKILL_IS_TYPE: | |
364 | if (event->type != id->type) | |
365 | return 0; | |
366 | break; | |
367 | case RFKILL_IS_INDEX: | |
368 | if (event->idx != id->index) | |
369 | return 0; | |
370 | break; | |
371 | case RFKILL_IS_ALL: | |
372 | break; | |
373 | default: | |
374 | abort(); | |
375 | } | |
376 | ||
377 | return 1; | |
378 | } | |
379 | ||
b3849c66 SK |
380 | static void fill_table_row(struct libscols_table *tb, struct rfkill_event *event) |
381 | { | |
382 | static struct libscols_line *ln; | |
383 | size_t i; | |
384 | ||
385 | assert(tb); | |
386 | ||
387 | ln = scols_table_new_line(tb, NULL); | |
388 | if (!ln) { | |
389 | errno = ENOMEM; | |
390 | errx(EXIT_FAILURE, _("failed to allocate output line")); | |
391 | } | |
392 | ||
393 | for (i = 0; i < (size_t)ncolumns; i++) { | |
394 | char *str = NULL; | |
395 | switch (get_column_id(i)) { | |
396 | case COL_DEVICE: | |
7d2a9960 | 397 | str = xstrdup(get_sys_attr(event->idx, "name")); |
b3849c66 SK |
398 | break; |
399 | case COL_ID: | |
400 | xasprintf(&str, "%" PRIu32, event->idx); | |
401 | break; | |
402 | case COL_TYPE: | |
7d2a9960 KZ |
403 | str = xstrdup(get_sys_attr(event->idx, "type")); |
404 | break; | |
405 | case COL_DESC: | |
406 | str = xstrdup(rfkill_type_to_desc(event->type)); | |
b3849c66 SK |
407 | break; |
408 | case COL_SOFT: | |
409 | str = xstrdup(event->soft ? _("blocked") : _("unblocked")); | |
410 | break; | |
411 | case COL_HARD: | |
412 | str = xstrdup(event->hard ? _("blocked") : _("unblocked")); | |
413 | break; | |
414 | default: | |
415 | abort(); | |
416 | } | |
417 | if (str && scols_line_refer_data(ln, i, str)) | |
418 | errx(EXIT_FAILURE, _("failed to add output data")); | |
419 | } | |
420 | } | |
421 | ||
7d2a9960 KZ |
422 | static int rfkill_list_old(const char *param) |
423 | { | |
424 | struct rfkill_id id = { .result = RFKILL_IS_ALL }; | |
425 | struct rfkill_event event; | |
426 | int fd, rc = 0; | |
427 | ||
428 | if (param) { | |
429 | id = rfkill_id_to_type(param); | |
430 | if (id.result == RFKILL_IS_INVALID) { | |
431 | warnx(_("invalid identifier: %s"), param); | |
432 | return -EINVAL; | |
433 | } | |
434 | } | |
435 | ||
20fae1bd | 436 | fd = rfkill_open(1, 1); |
06229abc | 437 | if (fd < 0) |
20fae1bd | 438 | return fd; |
7d2a9960 KZ |
439 | |
440 | while (1) { | |
441 | rc = rfkill_read_event(fd, &event); | |
442 | if (rc < 0) | |
443 | break; | |
444 | if (rc == 1 && errno == EAGAIN) { | |
445 | rc = 0; /* done */ | |
446 | break; | |
447 | } | |
448 | if (rc == 0 && event_match(&event, &id)) { | |
449 | char *name = xstrdup(get_sys_attr(event.idx, "name")), | |
450 | *type = xstrdup(rfkill_type_to_desc(event.type)); | |
451 | ||
452 | if (!type) | |
453 | type = xstrdup(get_sys_attr(event.idx, "type")); | |
454 | ||
455 | printf("%u: %s: %s\n", event.idx, name, type); | |
456 | printf("\tSoft blocked: %s\n", event.soft ? "yes" : "no"); | |
457 | printf("\tHard blocked: %s\n", event.hard ? "yes" : "no"); | |
458 | ||
459 | free(name); | |
460 | free(type); | |
461 | } | |
462 | } | |
463 | close(fd); | |
464 | return rc; | |
465 | } | |
466 | ||
4f892d77 | 467 | static void rfkill_list_init(struct control *ctrl) |
07d517c0 | 468 | { |
e8f6060f KZ |
469 | size_t i; |
470 | ||
b3849c66 | 471 | scols_init_debug(0); |
e8f6060f | 472 | |
4f892d77 SK |
473 | ctrl->tb = scols_new_table(); |
474 | if (!ctrl->tb) | |
b3849c66 SK |
475 | err(EXIT_FAILURE, _("failed to allocate output table")); |
476 | ||
4f892d77 | 477 | scols_table_enable_json(ctrl->tb, ctrl->json); |
63e523a1 | 478 | scols_table_set_name(ctrl->tb, "rfkilldevices"); |
4f892d77 SK |
479 | scols_table_enable_noheadings(ctrl->tb, ctrl->no_headings); |
480 | scols_table_enable_raw(ctrl->tb, ctrl->raw); | |
b3849c66 | 481 | |
e8f6060f KZ |
482 | for (i = 0; i < (size_t) ncolumns; i++) { |
483 | const struct colinfo *col = get_column_info(i); | |
2d6d789b | 484 | struct libscols_column *cl; |
b3849c66 | 485 | |
2d6d789b KZ |
486 | cl = scols_table_new_column(ctrl->tb, col->name, col->whint, col->flags); |
487 | if (!cl) | |
e8f6060f | 488 | err(EXIT_FAILURE, _("failed to allocate output column")); |
2d6d789b KZ |
489 | if (ctrl->json) { |
490 | int id = get_column_id(i); | |
491 | if (id == COL_ID) | |
492 | scols_column_set_json_type(cl, SCOLS_JSON_NUMBER); | |
493 | } | |
b3849c66 | 494 | } |
4f892d77 SK |
495 | } |
496 | ||
4f892d77 SK |
497 | static int rfkill_list_fill(struct control const *ctrl, const char *param) |
498 | { | |
499 | struct rfkill_id id = { .result = RFKILL_IS_ALL }; | |
500 | struct rfkill_event event; | |
49f4f7b6 | 501 | int fd, rc = 0; |
07d517c0 | 502 | |
a9405c24 DS |
503 | if (param) { |
504 | id = rfkill_id_to_type(param); | |
505 | if (id.result == RFKILL_IS_INVALID) { | |
367f4080 | 506 | warnx(_("invalid identifier: %s"), param); |
3c5022c6 | 507 | return -EINVAL; |
a9405c24 | 508 | } |
a9405c24 DS |
509 | } |
510 | ||
20fae1bd | 511 | fd = rfkill_open(1, 1); |
06229abc | 512 | if (fd < 0) |
20fae1bd | 513 | return fd; |
07d517c0 MH |
514 | |
515 | while (1) { | |
49f4f7b6 KZ |
516 | rc = rfkill_read_event(fd, &event); |
517 | if (rc < 0) | |
a9405c24 | 518 | break; |
49f4f7b6 KZ |
519 | if (rc == 1 && errno == EAGAIN) { |
520 | rc = 0; /* done */ | |
a9405c24 | 521 | break; |
a9405c24 | 522 | } |
49f4f7b6 KZ |
523 | if (rc == 0 && event_match(&event, &id)) |
524 | fill_table_row(ctrl->tb, &event); | |
07d517c0 | 525 | } |
07d517c0 | 526 | close(fd); |
49f4f7b6 | 527 | return rc; |
07d517c0 MH |
528 | } |
529 | ||
4f892d77 SK |
530 | static void rfkill_list_output(struct control const *ctrl) |
531 | { | |
532 | scols_print_table(ctrl->tb); | |
533 | scols_unref_table(ctrl->tb); | |
534 | } | |
535 | ||
20fae1bd | 536 | static int __rfkill_block(int fd, struct rfkill_id *id, uint8_t block, const char *param) |
07d517c0 | 537 | { |
b03378e7 SK |
538 | struct rfkill_event event = { |
539 | .op = RFKILL_OP_CHANGE_ALL, | |
540 | .soft = block, | |
541 | 0 | |
542 | }; | |
fd3d4ec7 | 543 | char *message = NULL; |
07d517c0 | 544 | |
20fae1bd | 545 | switch (id->result) { |
b03378e7 SK |
546 | case RFKILL_IS_INVALID: |
547 | warnx(_("invalid identifier: %s"), param); | |
3c5022c6 | 548 | return -1; |
fafd9121 | 549 | case RFKILL_IS_TYPE: |
20fae1bd | 550 | event.type = id->type; |
fd3d4ec7 | 551 | xasprintf(&message, "type %s", param); |
fafd9121 DS |
552 | break; |
553 | case RFKILL_IS_INDEX: | |
554 | event.op = RFKILL_OP_CHANGE; | |
20fae1bd KZ |
555 | event.idx = id->index; |
556 | xasprintf(&message, "id %d", id->index); | |
fafd9121 | 557 | break; |
b03378e7 | 558 | case RFKILL_IS_ALL: |
fd3d4ec7 | 559 | message = xstrdup("all"); |
367f4080 SK |
560 | break; |
561 | default: | |
562 | abort(); | |
94297403 | 563 | } |
b03378e7 | 564 | |
20fae1bd | 565 | if (write_all(fd, &event, sizeof(event)) != 0) |
367f4080 | 566 | warn(_("write failed: %s"), _PATH_DEV_RFKILL); |
3c5022c6 KZ |
567 | else { |
568 | openlog("rfkill", 0, LOG_USER); | |
569 | syslog(LOG_NOTICE, "%s set for %s", block ? "block" : "unblock", message); | |
570 | closelog(); | |
571 | } | |
fd3d4ec7 | 572 | free(message); |
20fae1bd KZ |
573 | return 0; |
574 | } | |
575 | ||
576 | static int rfkill_block(uint8_t block, const char *param) | |
577 | { | |
578 | struct rfkill_id id; | |
579 | int fd; | |
580 | ||
581 | id = rfkill_id_to_type(param); | |
582 | if (id.result == RFKILL_IS_INVALID) { | |
583 | warnx(_("invalid identifier: %s"), param); | |
584 | return -EINVAL; | |
585 | } | |
586 | ||
587 | fd = rfkill_open(0, 0); | |
588 | if (fd < 0) | |
589 | return fd; | |
590 | ||
591 | __rfkill_block(fd, &id, block, param); | |
592 | ||
b1209668 | 593 | return close(fd); |
07d517c0 MH |
594 | } |
595 | ||
20fae1bd KZ |
596 | static int rfkill_toggle(const char *param) |
597 | { | |
598 | struct rfkill_id id = { .result = RFKILL_IS_ALL }; | |
599 | struct rfkill_event event; | |
600 | int fd, rc = 0; | |
601 | ||
602 | id = rfkill_id_to_type(param); | |
603 | if (id.result == RFKILL_IS_INVALID) { | |
604 | warnx(_("invalid identifier: %s"), param); | |
605 | return -EINVAL; | |
606 | } | |
607 | ||
608 | fd = rfkill_open(0, 1); | |
609 | if (fd < 0) | |
610 | return fd; | |
611 | ||
612 | while (1) { | |
613 | rc = rfkill_read_event(fd, &event); | |
614 | if (rc < 0) | |
615 | break; | |
616 | if (rc == 1 && errno == EAGAIN) { | |
617 | rc = 0; /* done */ | |
618 | break; | |
619 | } | |
620 | if (rc == 0 && event_match(&event, &id)) | |
621 | __rfkill_block(fd, &id, event.soft ? 0 : 1, param); | |
622 | } | |
623 | ||
624 | close(fd); | |
625 | return rc; | |
626 | } | |
627 | ||
628 | ||
367f4080 | 629 | static void __attribute__((__noreturn__)) usage(void) |
07d517c0 | 630 | { |
b3849c66 | 631 | size_t i; |
47eaa870 | 632 | |
367f4080 | 633 | fputs(USAGE_HEADER, stdout); |
4f892d77 | 634 | fprintf(stdout, _(" %s [options] command [identifier ...]\n"), program_invocation_short_name); |
367f4080 SK |
635 | |
636 | fputs(USAGE_SEPARATOR, stdout); | |
637 | fputs(_("Tool for enabling and disabling wireless devices.\n"), stdout); | |
638 | ||
b3849c66 SK |
639 | fputs(USAGE_OPTIONS, stdout); |
640 | fputs(_(" -J, --json use JSON output format\n"), stdout); | |
641 | fputs(_(" -n, --noheadings don't print headings\n"), stdout); | |
642 | fputs(_(" -o, --output <list> define which output columns to use\n"), stdout); | |
4dfd172d | 643 | fputs(_(" --output-all output all columns\n"), stdout); |
b3849c66 SK |
644 | fputs(_(" -r, --raw use the raw output format\n"), stdout); |
645 | ||
646 | fputs(USAGE_SEPARATOR, stdout); | |
bad4c729 | 647 | fprintf(stdout, USAGE_HELP_OPTIONS(24)); |
b3849c66 | 648 | |
fcc3efb4 | 649 | fputs(USAGE_COLUMNS, stdout); |
b3849c66 | 650 | for (i = 0; i < ARRAY_SIZE(infos); i++) |
7d2a9960 | 651 | fprintf(stdout, " %-10s %s\n", infos[i].name, _(infos[i].help)); |
b3849c66 | 652 | |
fcc3efb4 KZ |
653 | fputs(USAGE_COMMANDS, stdout); |
654 | ||
367f4080 SK |
655 | /* |
656 | * TRANSLATORS: command names should not be translated, explaining | |
73afd3f8 | 657 | * them as additional field after identifier is fine, for example |
367f4080 SK |
658 | * |
659 | * list [identifier] (lista [tarkenne]) | |
660 | */ | |
661 | fputs(_(" help\n"), stdout); | |
662 | fputs(_(" event\n"), stdout); | |
663 | fputs(_(" list [identifier]\n"), stdout); | |
664 | fputs(_(" block identifier\n"), stdout); | |
665 | fputs(_(" unblock identifier\n"), stdout); | |
20fae1bd | 666 | fputs(_(" toggle identifier\n"), stdout); |
367f4080 | 667 | |
367f4080 SK |
668 | fprintf(stdout, USAGE_MAN_TAIL("rfkill(8)")); |
669 | exit(EXIT_SUCCESS); | |
d5fad6eb JB |
670 | } |
671 | ||
672 | int main(int argc, char **argv) | |
673 | { | |
b3849c66 | 674 | struct control ctrl = { 0 }; |
4dfd172d | 675 | int c, act = ACT_LIST, list_all = 0; |
b3849c66 | 676 | char *outarg = NULL; |
4dfd172d SK |
677 | enum { |
678 | OPT_LIST_TYPES = CHAR_MAX + 1 | |
679 | }; | |
367f4080 | 680 | static const struct option longopts[] = { |
b3849c66 SK |
681 | { "json", no_argument, NULL, 'J' }, |
682 | { "noheadings", no_argument, NULL, 'n' }, | |
683 | { "output", required_argument, NULL, 'o' }, | |
4dfd172d | 684 | { "output-all", no_argument, NULL, OPT_LIST_TYPES }, |
b3849c66 SK |
685 | { "raw", no_argument, NULL, 'r' }, |
686 | { "version", no_argument, NULL, 'V' }, | |
687 | { "help", no_argument, NULL, 'h' }, | |
367f4080 SK |
688 | { NULL, 0, NULL, 0 } |
689 | }; | |
b3849c66 SK |
690 | static const ul_excl_t excl[] = { |
691 | {'J', 'r'}, | |
692 | {0} | |
693 | }; | |
694 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
4f892d77 | 695 | int ret = 0; |
367f4080 SK |
696 | |
697 | setlocale(LC_ALL, ""); | |
698 | bindtextdomain(PACKAGE, LOCALEDIR); | |
699 | textdomain(PACKAGE); | |
2c308875 | 700 | close_stdout_atexit(); |
367f4080 | 701 | |
b3849c66 SK |
702 | while ((c = getopt_long(argc, argv, "Jno:rVh", longopts, NULL)) != -1) { |
703 | err_exclusive_options(c, longopts, excl, excl_st); | |
367f4080 | 704 | switch (c) { |
b3849c66 SK |
705 | case 'J': |
706 | ctrl.json = 1; | |
707 | break; | |
708 | case 'n': | |
709 | ctrl.no_headings = 1; | |
710 | break; | |
711 | case 'o': | |
712 | outarg = optarg; | |
713 | break; | |
4dfd172d SK |
714 | case OPT_LIST_TYPES: |
715 | list_all = 1; | |
716 | break; | |
b3849c66 SK |
717 | case 'r': |
718 | ctrl.raw = 1; | |
719 | break; | |
2c308875 | 720 | |
367f4080 | 721 | case 'V': |
2c308875 | 722 | print_version(EXIT_SUCCESS); |
367f4080 SK |
723 | case 'h': |
724 | usage(); | |
725 | default: | |
726 | errtryhelp(EXIT_FAILURE); | |
727 | } | |
b3849c66 SK |
728 | } |
729 | argc -= optind; | |
730 | argv += optind; | |
731 | ||
18d3a03f KZ |
732 | if (argc > 0) { |
733 | act = string_to_action(*argv); | |
734 | if (act < 0) | |
735 | errtryhelp(EXIT_FAILURE); | |
736 | argv++; | |
737 | argc--; | |
7d2a9960 KZ |
738 | |
739 | /* | |
740 | * For backward compatibility we use old output format if | |
20fae1bd | 741 | * "list" explicitly specified and --output not defined. |
7d2a9960 KZ |
742 | */ |
743 | if (!outarg && act == ACT_LIST) | |
744 | act = ACT_LIST_OLD; | |
18d3a03f KZ |
745 | } |
746 | ||
747 | switch (act) { | |
7d2a9960 KZ |
748 | case ACT_LIST_OLD: |
749 | /* Deprecated in favour of ACT_LIST */ | |
750 | if (!argc) | |
751 | ret |= rfkill_list_old(NULL); /* ALL */ | |
752 | else while (argc) { | |
753 | ret |= rfkill_list_old(*argv); | |
754 | argc--; | |
755 | argv++; | |
756 | } | |
757 | break; | |
758 | ||
18d3a03f | 759 | case ACT_LIST: |
b3849c66 SK |
760 | columns[ncolumns++] = COL_ID; |
761 | columns[ncolumns++] = COL_TYPE; | |
7d8e3bcd | 762 | columns[ncolumns++] = COL_DEVICE; |
4dfd172d SK |
763 | if (list_all) |
764 | columns[ncolumns++] = COL_DESC; | |
b3849c66 SK |
765 | columns[ncolumns++] = COL_SOFT; |
766 | columns[ncolumns++] = COL_HARD; | |
767 | ||
768 | if (outarg | |
769 | && string_add_to_idarray(outarg, columns, | |
770 | ARRAY_SIZE(columns), &ncolumns, | |
771 | column_name_to_id) < 0) | |
772 | return EXIT_FAILURE; | |
18d3a03f | 773 | |
4f892d77 | 774 | rfkill_list_init(&ctrl); |
18d3a03f KZ |
775 | if (!argc) |
776 | ret |= rfkill_list_fill(&ctrl, NULL); /* ALL */ | |
777 | else while (argc) { | |
4f892d77 | 778 | ret |= rfkill_list_fill(&ctrl, *argv); |
18d3a03f KZ |
779 | argc--; |
780 | argv++; | |
b3849c66 | 781 | } |
4f892d77 | 782 | rfkill_list_output(&ctrl); |
18d3a03f KZ |
783 | break; |
784 | ||
785 | case ACT_EVENT: | |
367f4080 | 786 | ret = rfkill_event(); |
18d3a03f KZ |
787 | break; |
788 | ||
789 | case ACT_HELP: | |
b3849c66 | 790 | usage(); |
18d3a03f KZ |
791 | break; |
792 | ||
793 | case ACT_BLOCK: | |
794 | while (argc) { | |
4f892d77 | 795 | ret |= rfkill_block(1, *argv); |
18d3a03f | 796 | argc--; |
4f892d77 | 797 | argv++; |
18d3a03f KZ |
798 | } |
799 | break; | |
800 | ||
801 | case ACT_UNBLOCK: | |
802 | while (argc) { | |
4f892d77 | 803 | ret |= rfkill_block(0, *argv); |
18d3a03f KZ |
804 | argv++; |
805 | argc--; | |
4f892d77 | 806 | } |
18d3a03f | 807 | break; |
20fae1bd KZ |
808 | |
809 | case ACT_TOGGLE: | |
810 | while (argc) { | |
811 | ret |= rfkill_toggle(*argv); | |
812 | argv++; | |
813 | argc--; | |
814 | } | |
815 | break; | |
18d3a03f | 816 | } |
07d517c0 | 817 | |
3c5022c6 | 818 | return ret ? EXIT_FAILURE : EXIT_SUCCESS; |
d5fad6eb | 819 | } |