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