From 6f9ab46448435568ce7378ebc4400623e9e0be8c Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Wed, 19 Mar 2014 13:10:30 +0100 Subject: [PATCH] Add new miracle-wifictl program The miracle-wifictl program can be used to manage miracle-wifid. It provides low-level P2P helpers, so the main miraclectl can skip thos. Signed-off-by: David Herrmann --- .gitignore | 1 + Makefile.am | 20 + src/ctl/ctl-cli.c | 369 ++++++++++++++ src/ctl/ctl-wifi.c | 1204 ++++++++++++++++++++++++++++++++++++++++++++ src/ctl/ctl.h | 239 +++++++++ src/ctl/wifictl.c | 533 ++++++++++++++++++++ 6 files changed, 2366 insertions(+) create mode 100644 src/ctl/ctl-cli.c create mode 100644 src/ctl/ctl-wifi.c create mode 100644 src/ctl/ctl.h create mode 100644 src/ctl/wifictl.c diff --git a/.gitignore b/.gitignore index 18a39b9..1b93630 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ configure libtool m4/ miracle-dhcp +miracle-wifictl miracle-wifid miraclectl miracled diff --git a/Makefile.am b/Makefile.am index 76d2969..24ea129 100644 --- a/Makefile.am +++ b/Makefile.am @@ -107,6 +107,26 @@ miracle_wifid_LDADD = \ $(DEPS_LIBS) miracle_wifid_LDFLAGS = $(AM_LDFLAGS) +# +# miracle-wifictl +# + +bin_PROGRAMS += miracle-wifictl + +miracle_wifictl_SOURCES = \ + src/ctl/ctl.h \ + src/ctl/ctl-cli.c \ + src/ctl/ctl-wifi.c \ + src/ctl/wifictl.c +miracle_wifictl_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(DEPS_CFLAGS) +miracle_wifictl_LDADD = \ + libmiracle-shared.la \ + -lreadline \ + $(DEPS_LIBS) +miracle_wifictl_LDFLAGS = $(AM_LDFLAGS) + # # miraclectl # diff --git a/src/ctl/ctl-cli.c b/src/ctl/ctl-cli.c new file mode 100644 index 0000000..33faf90 --- /dev/null +++ b/src/ctl/ctl-cli.c @@ -0,0 +1,369 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctl.h" +#include "shl_macro.h" +#include "shl_util.h" + +/* *sigh* readline doesn't include all their deps, so put them last */ +#include +#include + +/* + * Helpers for interactive commands + */ + +static sd_event *cli_event; +static sd_bus *cli_bus; +static sd_event_source *cli_sigs[_NSIG]; +static sd_event_source *cli_stdin; +static bool cli_rl; +static const struct cli_cmd *cli_cmds; +int cli_max_sev = LOG_NOTICE; + +static bool is_cli(void) +{ + return cli_rl; +} + +void cli_printv(const char *fmt, va_list args) +{ + SHL_PROTECT_ERRNO; + _shl_free_ char *line = NULL; + int point; + bool async; + + /* In case we print messages during readline() activity, we need to + * correctly save and restore RL-internal state. */ + async = is_cli() && !RL_ISSTATE(RL_STATE_DONE); + + if (async) { + point = rl_point; + line = rl_copy_text(0, rl_end); + rl_save_prompt(); + rl_replace_line("", 0); + rl_redisplay(); + } + + vprintf(fmt, args); + + if (async) { + rl_restore_prompt(); + rl_replace_line(line, 0); + rl_point = point; + rl_redisplay(); + } +} + +void cli_printf(const char *fmt, ...) +{ + SHL_PROTECT_ERRNO; + va_list args; + + va_start(args, fmt); + cli_printv(fmt, args); + va_end(args); +} + +int cli_help(const struct cli_cmd *cmds) +{ + unsigned int i; + + if (is_cli()) { + cli_printf("Available commands:\n"); + } else { + cli_fn_help(); + } + + for (i = 0; cmds[i].cmd; ++i) { + if (!cmds[i].desc) + continue; + if (is_cli() && cmds[i].cli_cmp == CLI_N) + continue; + if (!is_cli() && cmds[i].cli_cmp == CLI_Y) + continue; + + cli_printf(" %s %-*s %s\n", + cmds[i].cmd, + (int)(40 - strlen(cmds[i].cmd)), + cmds[i].args ? : "", + cmds[i].desc ? : ""); + } + + return 0; +} + +int cli_do(const struct cli_cmd *cmds, char **args, unsigned int n) +{ + unsigned int i; + const char *cmd; + int r; + + if (!n) + return -EAGAIN; + + cmd = *args++; + --n; + + for (i = 0; cmds[i].cmd; ++i) { + if (strcmp(cmd, cmds[i].cmd)) + continue; + if (is_cli() && cmds[i].cli_cmp == CLI_N) + continue; + if (!is_cli() && cmds[i].cli_cmp == CLI_Y) + continue; + + switch (cmds[i].argc_cmp) { + case CLI_EQUAL: + if (n != cmds[i].argc) { + cli_printf("Invalid number of arguments\n"); + return -EINVAL; + } + + break; + case CLI_MORE: + if (n < cmds[i].argc) { + cli_printf("too few arguments\n"); + return -EINVAL; + } + + break; + case CLI_LESS: + if (n > cmds[i].argc) { + cli_printf("too many arguments\n"); + return -EINVAL; + } + + break; + } + + if (cmds[i].fn) { + r = cmds[i].fn(args, n); + return (r == -EAGAIN) ? -EINVAL : r; + } + + break; + } + + if (!strcmp(cmd, "help")) + return cli_help(cmds); + + return -EAGAIN; +} + +static void cli_handler_fn(char *input) +{ + _shl_free_ char *original = input; + _shl_strv_free_ char **args = NULL; + int r; + + if (!input) { + rl_insert_text("quit"); + rl_redisplay(); + rl_crlf(); + sd_event_exit(cli_event, 0); + return; + } + + r = shl_qstr_tokenize(input, &args); + if (r < 0) + return cli_vENOMEM(); + else if (!r) + return; + + add_history(original); + r = cli_do(cli_cmds, args, r); + if (r != -EAGAIN) + return; + + cli_printf("Command not found\n"); +} + +static int cli_stdin_fn(sd_event_source *source, + int fd, + uint32_t mask, + void *data) +{ + if (mask & EPOLLIN) { + rl_callback_read_char(); + return 0; + } + + if (mask & (EPOLLHUP | EPOLLERR)) + sd_event_exit(cli_event, 0); + + return 0; +} + +static int cli_signal_fn(sd_event_source *source, + const struct signalfd_siginfo *ssi, + void *data) +{ + if (ssi->ssi_signo == SIGCHLD) { + cli_debug("caught SIGCHLD for %d", (int)ssi->ssi_pid); + } else if (ssi->ssi_signo == SIGINT) { + rl_replace_line("", 0); + rl_crlf(); + rl_on_new_line(); + rl_redisplay(); + } else { + cli_notice("caught signal %d, exiting..", + (int)ssi->ssi_signo); + sd_event_exit(cli_event, 0); + } + + return 0; +} + +void cli_destroy(void) +{ + unsigned int i; + + if (!cli_event) + return; + + if (cli_rl) { + cli_rl = false; + + rl_replace_line("", 0); + rl_crlf(); + rl_on_new_line(); + rl_redisplay(); + + rl_message(""); + rl_callback_handler_remove(); + } + + sd_event_source_unref(cli_stdin); + cli_stdin = NULL; + + for (i = 0; cli_sigs[i]; ++i) { + sd_event_source_unref(cli_sigs[i]); + cli_sigs[i] = NULL; + } + + cli_cmds = NULL; + sd_bus_detach_event(cli_bus); + cli_bus = NULL; + sd_event_unref(cli_event); + cli_event = NULL; +} + +int cli_init(sd_bus *bus, const struct cli_cmd *cmds) +{ + static const int sigs[] = { + SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGCHLD, 0 + }; + unsigned int i; + sigset_t mask; + int r; + + if (cli_event) + return cli_EINVAL(); + + r = sd_event_default(&cli_event); + if (r < 0) { + cli_vERR(r); + goto error; + } + + cli_cmds = cmds; + cli_bus = bus; + + r = sd_bus_attach_event(cli_bus, cli_event, 0); + if (r < 0) { + cli_vERR(r); + goto error; + } + + for (i = 0; sigs[i]; ++i) { + sigemptyset(&mask); + sigaddset(&mask, sigs[i]); + sigprocmask(SIG_BLOCK, &mask, NULL); + + r = sd_event_add_signal(cli_event, + &cli_sigs[i], + sigs[i], + cli_signal_fn, + NULL); + if (r < 0) { + cli_vERR(r); + goto error; + } + } + + r = sd_event_add_io(cli_event, + &cli_stdin, + fileno(stdin), + EPOLLHUP | EPOLLERR | EPOLLIN, + cli_stdin_fn, + NULL); + if (r < 0) { + cli_vERR(r); + goto error; + } + + cli_rl = true; + + rl_erase_empty_line = 1; + rl_callback_handler_install(NULL, cli_handler_fn); + + rl_set_prompt(CLI_PROMPT); + printf("\r"); + rl_on_new_line(); + rl_redisplay(); + + return 0; + +error: + cli_destroy(); + return r; +} + +int cli_run(void) +{ + if (!cli_event) + return cli_EINVAL(); + + return sd_event_loop(cli_event); +} + +void cli_exit(void) +{ + if (!cli_event) + return cli_vEINVAL(); + + sd_event_exit(cli_event, 0); +} + +bool cli_running(void) +{ + return is_cli(); +} diff --git a/src/ctl/ctl-wifi.c b/src/ctl/ctl-wifi.c new file mode 100644 index 0000000..3b86b38 --- /dev/null +++ b/src/ctl/ctl-wifi.c @@ -0,0 +1,1204 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#include +#include +#include +#include +#include +#include "ctl.h" +#include "shl_dlist.h" +#include "shl_macro.h" +#include "shl_util.h" +#include "util.h" + +/* + * Peers + */ + +static void ctl_peer_free(struct ctl_peer *p) +{ + if (!p) + return; + + if (shl_dlist_linked(&p->list)) + ctl_fn_peer_free(p); + + free(p->friendly_name); + free(p->p2p_mac); + + shl_dlist_unlink(&p->list); + free(p->label); + free(p); +} + +static int ctl_peer_new(struct ctl_peer **out, + struct ctl_link *l, + const char *label) +{ + struct ctl_peer *p; + int r; + + if (!l || !label) + return cli_EINVAL(); + + p = calloc(1, sizeof(*p)); + if (!p) + return cli_ENOMEM(); + + p->l = l; + + p->label = strdup(label); + if (!p->label) { + r = cli_ENOMEM(); + goto error; + } + + shl_dlist_link_tail(&l->peers, &p->list); + ctl_fn_peer_new(p); + if (out) + *out = p; + + return 0; + +error: + ctl_peer_free(p); + return r; +} + +static int ctl_peer_parse_properties(struct ctl_peer *p, + sd_bus_message *m) +{ + const char *t, *p2p_mac = NULL, *friendly_name = NULL; + const char *interface = NULL, *local_address = NULL; + const char *remote_address = NULL; + bool connected, connected_set = false; + char *tmp; + int r; + + if (!p || !m) + return cli_EINVAL(); + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); + if (r < 0) + return cli_log_parser(r); + + while ((r = sd_bus_message_enter_container(m, + 'e', + "sv")) > 0) { + r = sd_bus_message_read(m, "s", &t); + if (r < 0) + return cli_log_parser(r); + + if (!strcmp(t, "P2PMac")) { + r = bus_message_read_basic_variant(m, "s", &p2p_mac); + if (r < 0) + return cli_log_parser(r); + } else if (!strcmp(t, "FriendlyName")) { + r = bus_message_read_basic_variant(m, "s", + &friendly_name); + if (r < 0) + return cli_log_parser(r); + } else if (!strcmp(t, "Connected")) { + r = bus_message_read_basic_variant(m, "b", + &connected); + if (r < 0) + return cli_log_parser(r); + + connected_set = true; + } else if (!strcmp(t, "Interface")) { + r = bus_message_read_basic_variant(m, "s", + &interface); + if (r < 0) + return cli_log_parser(r); + } else if (!strcmp(t, "LocalAddress")) { + r = bus_message_read_basic_variant(m, "s", + &local_address); + if (r < 0) + return cli_log_parser(r); + } else if (!strcmp(t, "RemoteAddress")) { + r = bus_message_read_basic_variant(m, "s", + &remote_address); + if (r < 0) + return cli_log_parser(r); + } else { + sd_bus_message_skip(m, "v"); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + } + if (r < 0) + return cli_log_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + + if (p2p_mac) { + tmp = strdup(p2p_mac); + if (tmp) { + free(p->p2p_mac); + p->p2p_mac = tmp; + } else { + cli_ENOMEM(); + } + } + + if (friendly_name) { + tmp = strdup(friendly_name); + if (tmp) { + free(p->friendly_name); + p->friendly_name = tmp; + } else { + cli_ENOMEM(); + } + } + + if (connected_set && p->connected != connected) { + p->connected = connected; + if (p->connected) + ctl_fn_peer_connected(p); + else + ctl_fn_peer_disconnected(p); + } + + if (interface) { + tmp = strdup(interface); + if (tmp) { + free(p->interface); + p->interface = tmp; + } else { + cli_ENOMEM(); + } + } + + if (local_address) { + tmp = strdup(local_address); + if (tmp) { + free(p->local_address); + p->local_address = tmp; + } else { + cli_ENOMEM(); + } + } + + if (remote_address) { + tmp = strdup(remote_address); + if (tmp) { + free(p->remote_address); + p->remote_address = tmp; + } else { + cli_ENOMEM(); + } + } + + return 0; +} + +int ctl_peer_connect(struct ctl_peer *p, const char *prov, const char *pin) +{ + _sd_bus_message_unref_ sd_bus_message *m = NULL; + _sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL; + _shl_free_ char *node = NULL; + int r; + + if (!p) + return cli_EINVAL(); + + r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/peer", + p->label, + &node); + if (r < 0) + return cli_ERR(r); + + r = sd_bus_call_method(p->l->w->bus, + "org.freedesktop.miracle.wifi", + node, + "org.freedesktop.miracle.wifi.Peer", + "Connect", + &err, + NULL, + "ss", + prov ? : "auto", + pin ? : ""); + if (r < 0) { + cli_error("cannot connect peer %s: %s", + p->label, bus_error_message(&err, r)); + return r; + } + + return 0; +} + +int ctl_peer_disconnect(struct ctl_peer *p) +{ + _sd_bus_message_unref_ sd_bus_message *m = NULL; + _sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL; + _shl_free_ char *node = NULL; + int r; + + if (!p) + return cli_EINVAL(); + + r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/peer", + p->label, + &node); + if (r < 0) + return cli_ERR(r); + + r = sd_bus_call_method(p->l->w->bus, + "org.freedesktop.miracle.wifi", + node, + "org.freedesktop.miracle.wifi.Peer", + "Disconnect", + &err, + NULL, + NULL); + if (r < 0) { + cli_error("cannot disconnect peer %s: %s", + p->label, bus_error_message(&err, r)); + return r; + } + + return 0; +} + +/* + * Links + */ + +static struct ctl_peer *ctl_link_find_peer(struct ctl_link *l, + const char *label) +{ + struct shl_dlist *i; + struct ctl_peer *p; + + shl_dlist_for_each(i, &l->peers) { + p = peer_from_dlist(i); + if (!strcasecmp(p->label, label)) + return p; + } + + return NULL; +} + +static void ctl_link_free(struct ctl_link *l) +{ + struct ctl_peer *p; + + if (!l) + return; + + while (!shl_dlist_empty(&l->peers)) { + p = shl_dlist_last_entry(&l->peers, struct ctl_peer, list); + ctl_peer_free(p); + } + + if (shl_dlist_linked(&l->list)) + ctl_fn_link_free(l); + + free(l->friendly_name); + free(l->ifname); + + shl_dlist_unlink(&l->list); + free(l->label); + free(l); +} + +static int ctl_link_new(struct ctl_link **out, + struct ctl_wifi *w, + const char *label) +{ + struct ctl_link *l; + int r; + + if (!w || !label) + return cli_EINVAL(); + + l = calloc(1, sizeof(*l)); + if (!l) + return cli_ENOMEM(); + + l->w = w; + shl_dlist_init(&l->peers); + + l->label = strdup(label); + if (!l->label) { + r = cli_ENOMEM(); + goto error; + } + + shl_dlist_link_tail(&w->links, &l->list); + ctl_fn_link_new(l); + if (out) + *out = l; + + return 0; + +error: + ctl_link_free(l); + return r; +} + +static int ctl_link_parse_properties(struct ctl_link *l, + sd_bus_message *m) +{ + const char *t, *interface_name = NULL, *friendly_name = NULL; + unsigned int interface_index = 0; + bool p2p_scanning, p2p_scanning_set = false; + char *tmp; + int r; + + if (!l || !m) + return cli_EINVAL(); + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); + if (r < 0) + return cli_log_parser(r); + + while ((r = sd_bus_message_enter_container(m, + 'e', + "sv")) > 0) { + r = sd_bus_message_read(m, "s", &t); + if (r < 0) + return cli_log_parser(r); + + if (!strcmp(t, "InterfaceIndex")) { + r = bus_message_read_basic_variant(m, "u", + &interface_index); + if (r < 0) + return cli_log_parser(r); + } else if (!strcmp(t, "InterfaceName")) { + r = bus_message_read_basic_variant(m, "s", + &interface_name); + if (r < 0) + return cli_log_parser(r); + } else if (!strcmp(t, "FriendlyName")) { + r = bus_message_read_basic_variant(m, "s", + &friendly_name); + if (r < 0) + return cli_log_parser(r); + } else if (!strcmp(t, "P2PScanning")) { + r = bus_message_read_basic_variant(m, "b", + &p2p_scanning); + if (r < 0) + return cli_log_parser(r); + + p2p_scanning_set = true; + } else { + sd_bus_message_skip(m, "v"); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + } + if (r < 0) + return cli_log_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + + if (interface_index) + l->ifindex = interface_index; + + if (interface_name) { + tmp = strdup(interface_name); + if (tmp) { + free(l->ifname); + l->ifname = tmp; + } else { + cli_ENOMEM(); + } + } + + if (friendly_name) { + tmp = strdup(friendly_name); + if (tmp) { + free(l->friendly_name); + l->friendly_name = tmp; + } else { + cli_ENOMEM(); + } + } + + if (p2p_scanning_set) + l->p2p_scanning = p2p_scanning; + + return 0; +} + +int ctl_link_set_friendly_name(struct ctl_link *l, const char *name) +{ + _sd_bus_message_unref_ sd_bus_message *m = NULL; + _sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL; + _shl_free_ char *node = NULL; + int r; + + if (!l) + return cli_EINVAL(); + if (!strcmp(l->friendly_name, name)) + return 0; + + r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/link", + l->label, + &node); + if (r < 0) + return cli_ERR(r); + + r = sd_bus_message_new_method_call(l->w->bus, + &m, + "org.freedesktop.miracle.wifi", + node, + "org.freedesktop.DBus.Properties", + "Set"); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_append(m, "ss", + "org.freedesktop.miracle.wifi.Link", + "FriendlyName"); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_open_container(m, 'v', "s"); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_append(m, "s", name); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_call(l->w->bus, m, 0, &err, NULL); + if (r < 0) { + cli_error("cannot change friendly-name on link %s to %s: %s", + l->label, name, bus_error_message(&err, r)); + return r; + } + + return 0; +} + +int ctl_link_set_p2p_scanning(struct ctl_link *l, bool val) +{ + _sd_bus_message_unref_ sd_bus_message *m = NULL; + _sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL; + _shl_free_ char *node = NULL; + int r; + + if (!l) + return cli_EINVAL(); + if (l->p2p_scanning == val) + return 0; + + r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/link", + l->label, + &node); + if (r < 0) + return cli_ERR(r); + + r = sd_bus_message_new_method_call(l->w->bus, + &m, + "org.freedesktop.miracle.wifi", + node, + "org.freedesktop.DBus.Properties", + "Set"); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_append(m, "ss", + "org.freedesktop.miracle.wifi.Link", + "P2PScanning"); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_open_container(m, 'v', "b"); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_append(m, "b", val); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return cli_log_create(r); + + r = sd_bus_call(l->w->bus, m, 0, &err, NULL); + if (r < 0) { + cli_error("cannot change p2p-scanning state on link %s to %d: %s", + l->label, val, bus_error_message(&err, r)); + return r; + } + + /* Don't set l->p2p_scanning. We get a PropertiesChanged once the value + * has really changed. There is no synchronous way to get errors as the + * application couldn't deal with them, anyway. See the system-log if + * you want detailed information. + * However, mark the device as managed scan. This way, the app can stop + * scans during shutdown, if required. */ + + if (val) + l->have_p2p_scan = true; + + return 0; +} + +/* + * Wifi Management + */ + +static int ctl_wifi_parse_link(struct ctl_wifi *w, + const char *label, + sd_bus_message *m) +{ + const char *t; + struct ctl_link *l; + int r; + + r = ctl_link_new(&l, w, label); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); + if (r < 0) + return cli_log_parser(r); + + while ((r = sd_bus_message_enter_container(m, + 'e', + "sa{sv}")) > 0) { + r = sd_bus_message_read(m, "s", &t); + if (r < 0) + return cli_log_parser(r); + + if (strcmp(t, "org.freedesktop.miracle.wifi.Link")) { + r = sd_bus_message_skip(m, "a{sv}"); + if (r < 0) + return cli_log_parser(r); + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + continue; + } + + r = ctl_link_parse_properties(l, m); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + } + if (r < 0) + return cli_log_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + + return 0; +} + +static int ctl_wifi_parse_peer(struct ctl_wifi *w, + const char *label, + sd_bus_message *m) +{ + const char *t; + struct ctl_link *l; + struct ctl_peer *p; + int r; + + l = ctl_wifi_find_link_by_peer(w, label); + if (!l) + return cli_EINVAL(); + + r = ctl_peer_new(&p, l, label); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); + if (r < 0) + return cli_log_parser(r); + + while ((r = sd_bus_message_enter_container(m, + 'e', + "sa{sv}")) > 0) { + r = sd_bus_message_read(m, "s", &t); + if (r < 0) + return cli_log_parser(r); + + if (strcmp(t, "org.freedesktop.miracle.wifi.Peer")) { + r = sd_bus_message_skip(m, "a{sv}"); + if (r < 0) + return cli_log_parser(r); + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + continue; + } + + r = ctl_peer_parse_properties(p, m); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + } + if (r < 0) + return cli_log_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + + return 0; +} + +static int ctl_wifi_parse_object(struct ctl_wifi *w, + sd_bus_message *m, + bool added) +{ + _shl_free_ char *label = NULL; + struct ctl_link *l; + struct ctl_peer *p; + const char *t; + int r; + + r = sd_bus_message_read(m, "o", &t); + if (r < 0) + return cli_log_parser(r); + + r = sd_bus_path_decode(t, + "/org/freedesktop/miracle/wifi/link", + &label); + if (r < 0) { + return cli_ENOMEM(); + } else if (r > 0) { + l = ctl_wifi_find_link(w, label); + if (!l && added) { + return ctl_wifi_parse_link(w, label, m); + } else if (l && !added) { + /* We don't do any dynamic interfaces, so if any of + * them is removed, all of them are removed. */ + ctl_link_free(l); + } + } + + r = sd_bus_path_decode(t, + "/org/freedesktop/miracle/wifi/peer", + &label); + if (r < 0) { + return cli_ENOMEM(); + } else if (r > 0) { + p = ctl_wifi_find_peer(w, label); + if (!p && added) { + return ctl_wifi_parse_peer(w, label, m); + } else if (p && !added) { + /* We don't do any dynamic interfaces, so if any of + * them is removed, all of them are removed. */ + ctl_peer_free(p); + } + } + + /* skip unhandled payload */ + if (added) + r = sd_bus_message_skip(m, "a{sa{sv}}"); + else + r = sd_bus_message_skip(m, "as"); + if (r < 0) + return cli_log_parser(r); + + return 0; +} + +static int ctl_wifi_object_fn(sd_bus *bus, + sd_bus_message *m, + void *data, + sd_bus_error *err) +{ + struct ctl_wifi *w = data; + bool added; + + added = !strcmp(sd_bus_message_get_member(m), "InterfacesAdded"); + + return ctl_wifi_parse_object(w, m, added); +} + +static int ctl_wifi_properties_fn(sd_bus *bus, + sd_bus_message *m, + void *data, + sd_bus_error *err) +{ + _shl_free_ char *label = NULL; + struct ctl_wifi *w = data; + struct ctl_link *l; + struct ctl_peer *p; + const char *t; + int r; + + if (!sd_bus_message_is_signal(m, + "org.freedesktop.DBus.Properties", + "PropertiesChanged")) + return 0; + + t = sd_bus_message_get_path(m); + if (!t) + return cli_EINVAL(); + + r = sd_bus_path_decode(t, + "/org/freedesktop/miracle/wifi/link", + &label); + if (r < 0) { + return cli_ENOMEM(); + } else if (r > 0) { + l = ctl_wifi_find_link(w, label); + if (!l) + return 0; + + r = sd_bus_message_read(m, "s", &t); + if (r < 0) + return cli_log_parser(r); + + if (strcmp(t, "org.freedesktop.miracle.wifi.Link")) + return 0; + + return ctl_link_parse_properties(l, m); + } + + r = sd_bus_path_decode(t, + "/org/freedesktop/miracle/wifi/peer", + &label); + if (r < 0) { + return cli_ENOMEM(); + } else if (r > 0) { + p = ctl_wifi_find_peer(w, label); + if (!p) + return 0; + + r = sd_bus_message_read(m, "s", &t); + if (r < 0) + return cli_log_parser(r); + + if (strcmp(t, "org.freedesktop.miracle.wifi.Peer")) + return 0; + + return ctl_peer_parse_properties(p, m); + } + + return 0; +} + +static int ctl_wifi_peer_fn(sd_bus *bus, + sd_bus_message *m, + void *data, + sd_bus_error *err) +{ + _shl_free_ char *label = NULL; + struct ctl_wifi *w = data; + struct ctl_peer *p; + const char *t, *prov, *pin; + int r; + + if (!sd_bus_message_is_signal(m, + "org.freedesktop.miracle.wifi.Peer", + "ProvisionDiscovery")) + return 0; + + t = sd_bus_message_get_path(m); + if (!t) + return cli_EINVAL(); + + r = sd_bus_path_decode(t, + "/org/freedesktop/miracle/wifi/peer", + &label); + if (r < 0) { + return cli_ERR(r); + } else if (r > 0) { + p = ctl_wifi_find_peer(w, label); + if (!p) + return 0; + + r = sd_bus_message_read(m, "ss", &prov, &pin); + if (r < 0) + return cli_log_parser(r); + + ctl_fn_peer_provision_discovery(p, prov, pin); + } + + return 0; +} + +static int ctl_wifi_init(struct ctl_wifi *w) +{ + int r; + + r = sd_bus_add_match(w->bus, + "type='signal'," + "sender='org.freedesktop.miracle.wifi'," + "interface='org.freedesktop.DBus.ObjectManager'", + ctl_wifi_object_fn, + w); + if (r < 0) + return r; + + r = sd_bus_add_match(w->bus, + "type='signal'," + "sender='org.freedesktop.miracle.wifi'," + "interface='org.freedesktop.DBus.Properties'", + ctl_wifi_properties_fn, + w); + if (r < 0) + return r; + + r = sd_bus_add_match(w->bus, + "type='signal'," + "sender='org.freedesktop.miracle.wifi'," + "interface='org.freedesktop.miracle.wifi.Peer'", + ctl_wifi_peer_fn, + w); + if (r < 0) + return r; + + return 0; +} + +static void ctl_wifi_destroy(struct ctl_wifi *w) +{ + sd_bus_remove_match(w->bus, + "type='signal'," + "sender='org.freedesktop.miracle.wifi'," + "interface='org.freedesktop.miracle.wifi.Peer'", + ctl_wifi_peer_fn, + w); + sd_bus_remove_match(w->bus, + "type='signal'," + "sender='org.freedesktop.miracle.wifi'," + "interface='org.freedesktop.DBus.Properties'", + ctl_wifi_properties_fn, + w); + sd_bus_remove_match(w->bus, + "type='signal'," + "sender='org.freedesktop.miracle.wifi'," + "interface='org.freedesktop.DBus.ObjectManager'", + ctl_wifi_object_fn, + w); +} + +int ctl_wifi_new(struct ctl_wifi **out, sd_bus *bus) +{ + struct ctl_wifi *w; + int r; + + if (!out || !bus) + return cli_EINVAL(); + + w = calloc(1, sizeof(*w)); + if (!w) + return cli_ENOMEM(); + + w->bus = sd_bus_ref(bus); + shl_dlist_init(&w->links); + + r = ctl_wifi_init(w); + if (r < 0) { + cli_error("cannot initialize wifi-dbus objects"); + ctl_wifi_free(w); + return r; + } + + *out = w; + return 0; +} + +void ctl_wifi_free(struct ctl_wifi *w) +{ + struct ctl_link *l; + + if (!w) + return; + + while (!shl_dlist_empty(&w->links)) { + l = shl_dlist_last_entry(&w->links, struct ctl_link, list); + ctl_link_free(l); + } + + ctl_wifi_destroy(w); + sd_bus_unref(w->bus); + free(w); +} + +int ctl_wifi_fetch(struct ctl_wifi *w) +{ + _sd_bus_message_unref_ sd_bus_message *m = NULL; + _sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL; + int r; + + if (!w) + return cli_EINVAL(); + + r = sd_bus_call_method(w->bus, + "org.freedesktop.miracle.wifi", + "/org/freedesktop/miracle/wifi", + "org.freedesktop.DBus.ObjectManager", + "GetManagedObjects", + &err, + &m, + NULL); + if (r < 0) { + cli_error("cannot retrieve objects: %s", + bus_error_message(&err, r)); + return r; + } + + r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}"); + if (r < 0) + return cli_log_parser(r); + + while ((r = sd_bus_message_enter_container(m, + 'e', + "oa{sa{sv}}")) > 0) { + r = ctl_wifi_parse_object(w, m, true); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + } + if (r < 0) + return cli_log_parser(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return cli_log_parser(r); + + return 0; +} + +struct ctl_link *ctl_wifi_find_link(struct ctl_wifi *w, + const char *label) +{ + struct shl_dlist *i; + struct ctl_link *l; + + if (!w || shl_isempty(label)) + return NULL; + + shl_dlist_for_each(i, &w->links) { + l = link_from_dlist(i); + if (!strcasecmp(l->label, label)) + return l; + } + + return NULL; +} + +struct ctl_link *ctl_wifi_search_link(struct ctl_wifi *w, + const char *label) +{ + struct shl_dlist *i; + struct ctl_link *l; + + if (!w || shl_isempty(label)) + return NULL; + + l = ctl_wifi_find_link(w, label); + if (l) + return l; + + /* try matching on interface */ + shl_dlist_for_each(i, &w->links) { + l = link_from_dlist(i); + if (l->ifname && !strcasecmp(l->ifname, label)) + return l; + } + + /* try matching on friendly-name */ + shl_dlist_for_each(i, &w->links) { + l = link_from_dlist(i); + if (l->friendly_name && !strcasecmp(l->friendly_name, label)) + return l; + } + + return NULL; +} + +struct ctl_link *ctl_wifi_find_link_by_peer(struct ctl_wifi *w, + const char *label) +{ + const char *sep; + + if (!w || shl_isempty(label)) + return NULL; + + sep = strchr(label, '@'); + if (!sep) + return NULL; + + return ctl_wifi_find_link(w, sep + 1); +} + +struct ctl_link *ctl_wifi_search_link_by_peer(struct ctl_wifi *w, + const char *label) +{ + const char *sep; + + if (!w || shl_isempty(label)) + return NULL; + + sep = strchr(label, '@'); + if (!sep) + return NULL; + + return ctl_wifi_search_link(w, sep + 1); +} + +struct ctl_peer *ctl_wifi_find_peer(struct ctl_wifi *w, + const char *label) +{ + struct ctl_link *l; + + if (!w || shl_isempty(label)) + return NULL; + + /* first try exact match */ + l = ctl_wifi_find_link_by_peer(w, label); + if (!l) + return NULL; + + return ctl_link_find_peer(l, label); +} + +struct ctl_peer *ctl_wifi_search_peer(struct ctl_wifi *w, + const char *real_label) +{ + _shl_free_ char *label = NULL; + struct shl_dlist *i, *j; + struct ctl_link *l; + struct ctl_peer *p; + const char *next; + char *sep; + unsigned int cnt, idx; + + if (!w || shl_isempty(real_label)) + return NULL; + + label = strdup(real_label); + if (!label) { + cli_vENOMEM(); + return NULL; + } + + p = ctl_wifi_find_peer(w, label); + if (p) + return p; + + l = ctl_wifi_search_link_by_peer(w, label); + if (l) { + sep = strchr(label, '@'); + if (sep) + *sep = 0; + + shl_dlist_for_each(j, &l->peers) { + p = shl_dlist_entry(j, struct ctl_peer, list); + next = shl_startswith(p->label, label); + if (next && *next == '@') + return p; + } + + shl_dlist_for_each(j, &l->peers) { + p = shl_dlist_entry(j, struct ctl_peer, list); + if (p->friendly_name && + !strcasecmp(p->friendly_name, label)) + return p; + } + + shl_dlist_for_each(j, &l->peers) { + p = shl_dlist_entry(j, struct ctl_peer, list); + if (p->interface && + !strcasecmp(p->interface, label)) + return p; + } + + if (shl_atoi_u(label, 10, &next, &idx) >= 0 && !*next) { + cnt = 0; + shl_dlist_for_each(j, &l->peers) { + p = shl_dlist_entry(j, struct ctl_peer, list); + if (cnt++ == idx) + return p; + } + } + + if (sep) + *sep = '@'; + } + + shl_dlist_for_each(i, &w->links) { + l = shl_dlist_entry(i, struct ctl_link, list); + shl_dlist_for_each(j, &l->peers) { + p = shl_dlist_entry(j, struct ctl_peer, list); + next = shl_startswith(p->label, label); + if (next && *next == '@') + return p; + } + } + + shl_dlist_for_each(i, &w->links) { + l = shl_dlist_entry(i, struct ctl_link, list); + shl_dlist_for_each(j, &l->peers) { + p = shl_dlist_entry(j, struct ctl_peer, list); + if (p->friendly_name && + !strcasecmp(p->friendly_name, label)) + return p; + } + } + + shl_dlist_for_each(i, &w->links) { + l = shl_dlist_entry(i, struct ctl_link, list); + shl_dlist_for_each(j, &l->peers) { + p = shl_dlist_entry(j, struct ctl_peer, list); + if (p->interface && + !strcasecmp(p->interface, label)) + return p; + } + } + + if (shl_atoi_u(label, 10, &next, &idx) < 0 || *next) + return NULL; + + cnt = 0; + shl_dlist_for_each(i, &w->links) { + l = shl_dlist_entry(i, struct ctl_link, list); + shl_dlist_for_each(j, &l->peers) { + p = shl_dlist_entry(j, struct ctl_peer, list); + if (cnt++ == idx) + return p; + } + } + + return NULL; +} diff --git a/src/ctl/ctl.h b/src/ctl/ctl.h new file mode 100644 index 0000000..74df291 --- /dev/null +++ b/src/ctl/ctl.h @@ -0,0 +1,239 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "shl_dlist.h" + +#ifndef CTL_CTL_H +#define CTL_CTL_H + +struct ctl_wifi; +struct ctl_link; +struct ctl_peer; + +/* wifi handling */ + +struct ctl_peer { + struct shl_dlist list; + char *label; + struct ctl_link *l; + + /* properties */ + char *p2p_mac; + char *friendly_name; + bool connected; + char *interface; + char *local_address; + char *remote_address; +}; + +#define peer_from_dlist(_p) shl_dlist_entry((_p), struct ctl_peer, list); + +int ctl_peer_connect(struct ctl_peer *p, const char *prov, const char *pin); +int ctl_peer_disconnect(struct ctl_peer *p); + +struct ctl_link { + struct shl_dlist list; + char *label; + struct ctl_wifi *w; + + struct shl_dlist peers; + + bool have_p2p_scan; + + /* properties */ + unsigned int ifindex; + char *ifname; + char *friendly_name; + bool p2p_scanning; +}; + +#define link_from_dlist(_l) shl_dlist_entry((_l), struct ctl_link, list); + +int ctl_link_set_friendly_name(struct ctl_link *l, const char *name); +int ctl_link_set_p2p_scanning(struct ctl_link *l, bool val); + +struct ctl_wifi { + sd_bus *bus; + + struct shl_dlist links; +}; + +int ctl_wifi_new(struct ctl_wifi **out, sd_bus *bus); +void ctl_wifi_free(struct ctl_wifi *w); +int ctl_wifi_fetch(struct ctl_wifi *w); + +struct ctl_link *ctl_wifi_find_link(struct ctl_wifi *w, + const char *label); +struct ctl_link *ctl_wifi_search_link(struct ctl_wifi *w, + const char *label); +struct ctl_link *ctl_wifi_find_link_by_peer(struct ctl_wifi *w, + const char *label); +struct ctl_link *ctl_wifi_search_link_by_peer(struct ctl_wifi *w, + const char *label); +struct ctl_peer *ctl_wifi_find_peer(struct ctl_wifi *w, + const char *label); +struct ctl_peer *ctl_wifi_search_peer(struct ctl_wifi *w, + const char *real_label); + +/* CLI handling */ + +extern int cli_max_sev; +void cli_printv(const char *fmt, va_list args); +void cli_printf(const char *fmt, ...); + +enum { +#ifndef LOG_FATAL + LOG_FATAL = 0, +#endif +#ifndef LOG_ALERT + LOG_ALERT = 1, +#endif +#ifndef LOG_CRITICAL + LOG_CRITICAL = 2, +#endif +#ifndef LOG_ERROR + LOG_ERROR = 3, +#endif +#ifndef LOG_WARNING + LOG_WARNING = 4, +#endif +#ifndef LOG_NOTICE + LOG_NOTICE = 5, +#endif +#ifndef LOG_INFO + LOG_INFO = 6, +#endif +#ifndef LOG_DEBUG + LOG_DEBUG = 7, +#endif + LOG_SEV_NUM, +}; + +#define cli_log(_fmt, ...) \ + cli_printf(_fmt "\n", ##__VA_ARGS__) +#define cli_log_fn(_fmt, ...) \ + cli_printf(_fmt " (%s() in %s:%d)\n", ##__VA_ARGS__, __func__, __FILE__, __LINE__) +#define cli_error(_fmt, ...) \ + ((LOG_ERROR <= cli_max_sev) ? \ + cli_log_fn("ERROR: " _fmt, ##__VA_ARGS__) : (void)0) +#define cli_warning(_fmt, ...) \ + ((LOG_WARNING <= cli_max_sev) ? \ + cli_log_fn("WARNING: " _fmt, ##__VA_ARGS__) : (void)0) +#define cli_notice(_fmt, ...) \ + ((LOG_NOTICE <= cli_max_sev) ? \ + cli_log("NOTICE: " _fmt, ##__VA_ARGS__) : (void)0) +#define cli_debug(_fmt, ...) \ + ((LOG_DEBUG <= cli_max_sev) ? \ + cli_log_fn("DEBUG: " _fmt, ##__VA_ARGS__) : (void)0) + +#define cli_EINVAL() \ + (cli_error("invalid arguments"), -EINVAL) +#define cli_vEINVAL() \ + ((void)cli_EINVAL()) + +#define cli_EFAULT() \ + (cli_error("internal operation failed"), -EFAULT) +#define cli_vEFAULT() \ + ((void)cli_EFAULT()) + +#define cli_ENOMEM() \ + (cli_error("out of memory"), -ENOMEM) +#define cli_vENOMEM() \ + ((void)cli_ENOMEM()) + +#define cli_EPIPE() \ + (cli_error("fd closed unexpectedly"), -EPIPE) +#define cli_vEPIPE() \ + ((void)cli_EPIPE()) + +#define cli_ERRNO() \ + (cli_error("syscall failed (%d): %m", errno), -errno) +#define cli_vERRNO() \ + ((void)cli_ERRNO()) + +#define cli_ERR(_r) \ + (errno = -(_r), cli_error("syscall failed (%d): %m", (_r)), (_r)) +#define cli_vERR(_r) \ + ((void)cli_ERR(_r)) + +#define cli_log_parser(_r) \ + (cli_error("cannot parse dbus message: %s", \ + strerror((_r) < 0 ? -(_r) : (_r))), (_r)) + +#define cli_log_create(_r) \ + (cli_error("cannot create dbus message: %s", \ + strerror((_r) < 0 ? -(_r) : (_r))), (_r)) + +#define CLI_DEFAULT "\x1B[0m" +#define CLI_RED "\x1B[0;91m" +#define CLI_GREEN "\x1B[0;92m" +#define CLI_YELLOW "\x1B[0;93m" +#define CLI_BLUE "\x1B[0;94m" +#define CLI_BOLDGRAY "\x1B[1;30m" +#define CLI_BOLDWHITE "\x1B[1;37m" +#define CLI_PROMPT CLI_BLUE "[miraclectl] # " CLI_DEFAULT + +struct cli_cmd { + const char *cmd; + const char *args; + enum { + CLI_N, /* no */ + CLI_M, /* maybe */ + CLI_Y, /* yes */ + } cli_cmp; + enum { + CLI_MORE, + CLI_LESS, + CLI_EQUAL, + } argc_cmp; + int argc; + int (*fn) (char **args, unsigned int n); + const char *desc; +}; + +int cli_init(sd_bus *bus, const struct cli_cmd *cmds); +void cli_destroy(void); +int cli_run(void); +void cli_exit(void); +bool cli_running(void); + +int cli_help(const struct cli_cmd *cmds); +int cli_do(const struct cli_cmd *cmds, char **args, unsigned int n); + +/* callback functions */ + +void ctl_fn_peer_new(struct ctl_peer *p); +void ctl_fn_peer_free(struct ctl_peer *p); +void ctl_fn_peer_provision_discovery(struct ctl_peer *p, + const char *prov, + const char *pin); +void ctl_fn_peer_connected(struct ctl_peer *p); +void ctl_fn_peer_disconnected(struct ctl_peer *p); +void ctl_fn_link_new(struct ctl_link *l); +void ctl_fn_link_free(struct ctl_link *l); + +void cli_fn_help(void); + +#endif /* CTL_CTL_H */ diff --git a/src/ctl/wifictl.c b/src/ctl/wifictl.c new file mode 100644 index 0000000..dd6839c --- /dev/null +++ b/src/ctl/wifictl.c @@ -0,0 +1,533 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * MiracleCast is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * MiracleCast is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with MiracleCast; If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctl.h" +#include "shl_macro.h" +#include "shl_util.h" + +static sd_bus *bus; +static struct ctl_wifi *wifi; + +static struct ctl_link *selected_link; + +/* + * cmd list + */ + +static int cmd_list(char **args, unsigned int n) +{ + size_t link_cnt = 0, peer_cnt = 0; + struct shl_dlist *i, *j; + struct ctl_link *l; + struct ctl_peer *p; + + /* list links */ + + cli_printf("%6s %-24s %-30s\n", + "LINK", "INTERFACE", "FRIENDLY-NAME"); + + shl_dlist_for_each(i, &wifi->links) { + l = link_from_dlist(i); + ++link_cnt; + + cli_printf("%6s %-24s %-30s\n", + l->label, + shl_isempty(l->ifname) ? + "" : l->ifname, + shl_isempty(l->friendly_name) ? + "" : l->friendly_name); + } + + cli_printf("\n"); + + /* list peers */ + + cli_printf("%6s %-24s %-30s %-10s\n", + "LINK", "PEER-ID", "FRIENDLY-NAME", "CONNECTED"); + + shl_dlist_for_each(i, &wifi->links) { + l = link_from_dlist(i); + + shl_dlist_for_each(j, &l->peers) { + p = peer_from_dlist(j); + ++peer_cnt; + + cli_printf("%6s %-24s %-30s %-10s\n", + p->l->label, + p->label, + shl_isempty(p->friendly_name) ? + "" : p->friendly_name, + p->connected ? "yes" : "no"); + } + } + + cli_printf("\n %u peers and %u links listed.\n", peer_cnt, link_cnt); + + return 0; +} + +/* + * cmd: select + */ + +static int cmd_select(char **args, unsigned int n) +{ + struct ctl_link *l; + + if (!n) { + if (selected_link) { + cli_printf("link %s deselected\n", + selected_link->label); + selected_link = NULL; + } + + return 0; + } + + l = ctl_wifi_search_link(wifi, args[0]); + if (!l) { + cli_error("unknown link %s", args[0]); + return 0; + } + + selected_link = l; + cli_printf("link %s selected\n", selected_link->label); + + return 0; +} + +/* + * cmd: show + */ + +static int cmd_show(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + struct ctl_peer *p = NULL; + + if (n > 0) { + if (!(l = ctl_wifi_find_link(wifi, args[0])) && + !(p = ctl_wifi_find_peer(wifi, args[0])) && + !(l = ctl_wifi_search_link(wifi, args[0])) && + !(p = ctl_wifi_search_peer(wifi, args[0]))) { + cli_error("unknown link or peer %s", args[0]); + return 0; + } + } else { + l = selected_link; + } + + if (l) { + cli_printf("Link=%s\n", l->label); + if (l->ifindex > 0) + cli_printf("InterfaceIndex=%u\n", l->ifindex); + if (l->ifname && *l->ifname) + cli_printf("InterfaceName=%s\n", l->ifname); + if (l->friendly_name && *l->friendly_name) + cli_printf("FriendlyName=%s\n", l->friendly_name); + cli_printf("P2PScanning=%d\n", l->p2p_scanning); + } else if (p) { + cli_printf("Peer=%s\n", p->label); + if (p->p2p_mac && *p->p2p_mac) + cli_printf("P2PMac=%s\n", p->p2p_mac); + if (p->friendly_name && *p->friendly_name) + cli_printf("FriendlyName=%s\n", p->friendly_name); + cli_printf("Connected=%d\n", p->connected); + if (p->interface && *p->interface) + cli_printf("Interface=%s\n", p->interface); + if (p->local_address && *p->local_address) + cli_printf("LocalAddress=%s\n", p->local_address); + if (p->remote_address && *p->remote_address) + cli_printf("RemoteAddress=%s\n", p->remote_address); + } else { + cli_printf("Show what?\n"); + return 0; + } + + return 0; +} + +/* + * cmd: set-friendly-name + */ + +static int cmd_set_friendly_name(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + const char *name; + + if (n < 1) { + cli_printf("To what?\n"); + return 0; + } + + if (n > 1) { + l = ctl_wifi_search_link(wifi, args[0]); + if (!l) { + cli_error("unknown link %s", args[0]); + return 0; + } + + name = args[1]; + } else { + name = args[0]; + } + + l = l ? : selected_link; + if (!l) { + cli_error("no link selected"); + return 0; + } + + return ctl_link_set_friendly_name(l, name); +} + +/* + * cmd: p2p-scan + */ + +static int cmd_p2p_scan(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + unsigned int i; + bool stop = false; + + for (i = 0; i < n; ++i) { + if (!strcmp(args[i], "stop")) { + stop = true; + } else { + l = ctl_wifi_search_link(wifi, args[i]); + if (!l) { + cli_error("unknown link %s", args[i]); + return 0; + } + } + } + + l = l ? : selected_link; + if (!l) { + cli_error("no link selected"); + return 0; + } + + return ctl_link_set_p2p_scanning(l, !stop); +} + +/* + * cmd: connect + */ + +static bool is_valid_prov(const char *prov) +{ + return prov && (!strcmp(prov, "auto") || + !strcmp(prov, "pbc") || + !strcmp(prov, "display") || + !strcmp(prov, "pin")); +} + +static int cmd_connect(char **args, unsigned int n) +{ + struct ctl_peer *p; + const char *prov, *pin; + + if (n < 1) { + cli_printf("To whom?\n"); + return 0; + } + + p = ctl_wifi_search_peer(wifi, args[0]); + if (!p) { + cli_error("unknown peer %s", args[0]); + return 0; + } + + if (n > 2) { + prov = args[1]; + pin = args[2]; + } else if (n > 1) { + if (is_valid_prov(args[1])) { + prov = args[1]; + pin = ""; + } else { + prov = "auto"; + pin = args[1]; + } + } else { + prov = "auto"; + pin = ""; + } + + return ctl_peer_connect(p, prov, pin); +} + +/* + * cmd: disconnect + */ + +static int cmd_disconnect(char **args, unsigned int n) +{ + struct ctl_peer *p; + + if (n < 1) { + cli_printf("From whom?\n"); + return 0; + } + + p = ctl_wifi_search_peer(wifi, args[0]); + if (!p) { + cli_error("unknown peer %s", args[0]); + return 0; + } + + return ctl_peer_disconnect(p); +} + +/* + * cmd: quit/exit + */ + +static int cmd_quit(char **args, unsigned int n) +{ + cli_exit(); + return 0; +} + +/* + * main + */ + +static const struct cli_cmd cli_cmds[] = { + { "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" }, + { "select", "[link]", CLI_Y, CLI_LESS, 1, cmd_select, "Select default link" }, + { "show", "[link|peer]", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" }, + { "set-friendly-name", "[link] ", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object" }, + { "p2p-scan", "[link] [stop]", CLI_Y, CLI_LESS, 2, cmd_p2p_scan, "Control neighborhood P2P scanning" }, + { "connect", " [provision] [pin]", CLI_M, CLI_LESS, 3, cmd_connect, "Connect to peer" }, + { "disconnect", "", CLI_M, CLI_EQUAL, 1, cmd_disconnect, "Disconnect from peer" }, + { "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" }, + { "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL }, + { "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" }, + { }, +}; + +void ctl_fn_peer_new(struct ctl_peer *p) +{ + if (cli_running()) + cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Peer: %s\n", + p->label); +} + +void ctl_fn_peer_free(struct ctl_peer *p) +{ + if (cli_running()) + cli_printf("[" CLI_RED "REMOVE" CLI_DEFAULT "] Peer: %s\n", + p->label); +} + +void ctl_fn_peer_provision_discovery(struct ctl_peer *p, + const char *prov, + const char *pin) +{ + if (cli_running()) + cli_printf("[" CLI_YELLOW "PROV" CLI_DEFAULT "] Peer: %s Type: %s PIN: %s\n", + p->label, prov, pin); +} + +void ctl_fn_peer_connected(struct ctl_peer *p) +{ + if (cli_running()) + cli_printf("[" CLI_GREEN "CONNECT" CLI_DEFAULT "] Peer: %s\n", + p->label); +} + +void ctl_fn_peer_disconnected(struct ctl_peer *p) +{ + if (cli_running()) + cli_printf("[" CLI_YELLOW "DISCONNECT" CLI_DEFAULT "] Peer: %s\n", + p->label); +} + +void ctl_fn_link_new(struct ctl_link *l) +{ + if (cli_running()) + cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Link: %s\n", + l->label); +} + +void ctl_fn_link_free(struct ctl_link *l) +{ + if (l == selected_link) { + cli_printf("link %s deselected\n", + selected_link->label); + selected_link = NULL; + } + + if (cli_running()) + cli_printf("[" CLI_RED "REMOVE" CLI_DEFAULT "] Link: %s\n", + l->label); +} + +void cli_fn_help() +{ + /* + * 80-char barrier: + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + */ + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control command to or query the MiracleCast Wifi-Manager. If no arguments\n" + "are given, an interactive command-line tool is provided.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --log-level Maximum level for log messages\n" + "\n" + "Commands:\n" + , program_invocation_short_name); + /* + * 80-char barrier: + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + */ +} + +static int ctl_interactive(void) +{ + struct shl_dlist *i; + struct ctl_link *l; + int r; + + r = cli_init(bus, cli_cmds); + if (r < 0) + return r; + + r = ctl_wifi_fetch(wifi); + if (r < 0) + goto error; + + r = cli_run(); + + /* stop managed scans, but only in interactive mode */ + shl_dlist_for_each(i, &wifi->links) { + l = link_from_dlist(i); + if (l->have_p2p_scan) + ctl_link_set_p2p_scanning(l, false); + } + +error: + cli_destroy(); + return r; +} + +static int ctl_single(char **argv, int argc) +{ + int r; + + r = ctl_wifi_fetch(wifi); + if (r < 0) + return r; + + r = cli_do(cli_cmds, argv, argc); + if (r == -EAGAIN) + cli_error("unknown operation %s", argv[0]); + + return r; +} + +static int ctl_main(int argc, char *argv[]) +{ + int r, left; + + r = ctl_wifi_new(&wifi, bus); + if (r < 0) + return r; + + left = argc - optind; + if (left <= 0) + r = ctl_interactive(); + else + r = ctl_single(argv + optind, left); + + ctl_wifi_free(wifi); + return r; +} + +static int parse_argv(int argc, char *argv[]) +{ + enum { + ARG_VERSION = 0x100, + ARG_LOG_LEVEL, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + {} + }; + int c; + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + switch (c) { + case 'h': + return cli_help(cli_cmds); + case ARG_VERSION: + puts(PACKAGE_STRING); + return 0; + case ARG_LOG_LEVEL: + cli_max_sev = atoi(optarg); + break; + case '?': + return -EINVAL; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + int r; + + setlocale(LC_ALL, ""); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (!r) + return EXIT_SUCCESS; + + r = sd_bus_default_system(&bus); + if (r < 0) { + cli_error("cannot connect to system bus: %s", strerror(-r)); + return EXIT_FAILURE; + } + + r = ctl_main(argc, argv); + sd_bus_unref(bus); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +}