From 650920082c78df8cea2c4e77a29563d7717e0bca Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 11 Feb 2014 11:34:33 +0100 Subject: [PATCH] miraclectl: use readline Add readline support to miraclectl so we can have interactive commands. This rewrites huge parts of miraclectl but hopefully makes it future proof so no such rewrite will be needed again. Signed-off-by: David Herrmann --- Makefile.am | 1 + configure.ac | 2 + src/miraclectl.c | 1471 ++++++++++++++++++++++++++++++---------------- 3 files changed, 956 insertions(+), 518 deletions(-) diff --git a/Makefile.am b/Makefile.am index cc2f380..141a680 100644 --- a/Makefile.am +++ b/Makefile.am @@ -119,6 +119,7 @@ miraclectl_CPPFLAGS = \ $(DEPS_CFLAGS) miraclectl_LDADD = \ libshl.la \ + -lreadline \ $(DEPS_LIBS) miraclectl_LDFLAGS = $(AM_LDFLAGS) diff --git a/configure.ac b/configure.ac index 524720b..e1e0e59 100644 --- a/configure.ac +++ b/configure.ac @@ -43,6 +43,8 @@ LT_INIT PKG_CHECK_MODULES([DEPS], [libwfd libsystemd-daemon >= 208]) PKG_CHECK_MODULES([GDHCP], [glib-2.0]) +AC_CHECK_HEADERS(readline/readline.h,, AC_MSG_ERROR(GNU readline not found)) + # # Test for "check" which we use for our test-suite. If not found, we disable # all tests. diff --git a/src/miraclectl.c b/src/miraclectl.c index 6bf9f52..f925a6d 100644 --- a/src/miraclectl.c +++ b/src/miraclectl.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -34,30 +35,311 @@ #include #include #include "miracle.h" -#include "shl_log.h" #include "shl_macro.h" #include "shl_util.h" +/* *sigh* readline doesn't include all their deps, so put them last */ +#include +#include + +static sd_bus *bus; +static char *selected_link; + /* * Helpers for interactive commands */ -static sd_bus *cli_bus; static sd_event *cli_event; static sd_event_source *cli_sigs[_NSIG]; +static sd_event_source *cli_stdin; +static bool cli_rl; +static int cli_max_sev; + +#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 + +static const struct cli_cmd { + const char *cmd; + const char *args; + enum { + CLI_N, /* no */ + CLI_M, /* maybe */ + CLI_Y, /* yes */ + } cli_cmp; + enum { + MORE, + LESS, + EQUAL, + } argc_cmp; + int argc; + int (*fn) (char **args, unsigned int n); + const char *desc; +} cli_cmds[]; + +static bool is_cli(void) +{ + return cli_rl; +} + +static void cli_printv(const char *fmt, va_list args) +{ + SHL_PROTECT_ERRNO; + _shl_cleanup_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(); + } +} + +static void cli_printf(const char *fmt, ...) +{ + SHL_PROTECT_ERRNO; + va_list args; + + va_start(args, fmt); + cli_printv(fmt, args); + va_end(args); +} + +#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)) + +static int cli_help(void) +{ + unsigned int i; + + if (is_cli()) { + cli_printf("Available commands:\n"); + } else { + /* + * 80-char barrier: + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + */ + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control command to or query the MiracleCast manager. If no arguments are\n" + "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 + */ + } + + for (i = 0; cli_cmds[i].cmd; ++i) { + if (!cli_cmds[i].desc) + continue; + if (is_cli() && cli_cmds[i].cli_cmp == CLI_N) + continue; + if (!is_cli() && cli_cmds[i].cli_cmp == CLI_Y) + continue; + + cli_printf(" %s %-*s %s\n", + cli_cmds[i].cmd, + (int)(25 - strlen(cli_cmds[i].cmd)), + cli_cmds[i].args ? : "", + cli_cmds[i].desc ? : ""); + } + + return 0; +} + +static int cli_do(char **args, unsigned int n) +{ + unsigned int i; + const char *cmd; + int r; + + if (!n) + return -EAGAIN; + + cmd = *args++; + --n; + + for (i = 0; cli_cmds[i].cmd; ++i) { + if (strcmp(cmd, cli_cmds[i].cmd)) + continue; + if (is_cli() && cli_cmds[i].cli_cmp == CLI_N) + continue; + if (!is_cli() && cli_cmds[i].cli_cmp == CLI_Y) + continue; + + switch (cli_cmds[i].argc_cmp) { + case EQUAL: + if (n != cli_cmds[i].argc) { + cli_printf("Invalid number of arguments\n"); + return -EINVAL; + } + + break; + case MORE: + if (n < cli_cmds[i].argc) { + cli_printf("too few arguments\n"); + return -EINVAL; + } + + break; + case LESS: + if (n > cli_cmds[i].argc) { + cli_printf("too many arguments\n"); + return -EINVAL; + } + + break; + } + + if (cli_cmds[i].fn) { + r = cli_cmds[i].fn(args, n); + return (r == -EAGAIN) ? -EINVAL : r; + } + + break; + } + + if (!strcmp(cmd, "help")) + return cli_help(); + + return -EAGAIN; +} + +static void cli_handler_fn(char *input) +{ + _shl_cleanup_free_ char *original = input; + _shl_cleanup_strv_ 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(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) { - log_debug("caught SIGCHLD for %d", (int)ssi->ssi_pid); - return 0; + 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); } - log_notice("caught signal %d, exiting..", (int)ssi->ssi_signo); - sd_event_exit(cli_event, 0); - return 0; } @@ -65,9 +347,24 @@ static void cli_destroy(void) { unsigned int i; - if (!cli_bus) + 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; @@ -75,11 +372,9 @@ static void cli_destroy(void) sd_event_unref(cli_event); cli_event = NULL; - sd_bus_unref(cli_bus); - cli_bus = NULL; } -static int cli_init(sd_bus *bus) +static int cli_init(void) { static const int sigs[] = { SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGCHLD, 0 @@ -88,20 +383,18 @@ static int cli_init(sd_bus *bus) sigset_t mask; int r; - if (cli_bus || !bus) - return log_EINVAL(); - - cli_bus = sd_bus_ref(bus); + if (cli_event) + return cli_EINVAL(); r = sd_event_default(&cli_event); if (r < 0) { - log_vERR(r); + cli_vERR(r); goto error; } - r = sd_bus_attach_event(cli_bus, cli_event, 0); + r = sd_bus_attach_event(bus, cli_event, 0); if (r < 0) { - log_vERR(r); + cli_vERR(r); goto error; } @@ -116,11 +409,32 @@ static int cli_init(sd_bus *bus) NULL, &cli_sigs[i]); if (r < 0) { - log_vERR(r); + cli_vERR(r); goto error; } } + r = sd_event_add_io(cli_event, + fileno(stdin), + EPOLLHUP | EPOLLERR | EPOLLIN, + cli_stdin_fn, + NULL, + &cli_stdin); + 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: @@ -130,189 +444,191 @@ error: static int cli_run(void) { - if (!cli_bus) - return log_EINVAL(); + if (!cli_event) + return cli_EINVAL(); return sd_event_loop(cli_event); } /* - * verb: list + * cmd list */ -static int verb_list_link(sd_bus *bus, sd_bus_message *m, const char *link) +static int cmd_list_link(sd_bus_message *m, const char *link) { const char *obj, *name = ""; int r; r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); while ((r = sd_bus_message_enter_container(m, 'e', "sa{sv}")) > 0) { r = sd_bus_message_read(m, "s", &obj); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); if (strcmp(obj, "org.freedesktop.miracle.Link")) { r = sd_bus_message_skip(m, "a{sv}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); continue; } r = sd_bus_message_enter_container(m, 'a', "{sv}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); while ((r = sd_bus_message_enter_container(m, 'e', "sv")) > 0) { r = sd_bus_message_read(m, "s", &obj); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); if (!strcmp(obj, "Name")) { r = bus_message_read_basic_variant(m, "s", &name); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } else { sd_bus_message_skip(m, "v"); } r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); - printf("%16s %-24s\n", link, name); + cli_printf("%16s %-24s\n", link, name); return 0; } -static int verb_list_links(sd_bus *bus, sd_bus_message *m) +static int cmd_list_links(sd_bus_message *m) { - _cleanup_free_ char *link = NULL; + _shl_cleanup_free_ char *link = NULL; unsigned int link_cnt = 0; const char *obj; int r; - printf("%16s %-24s\n", "LINK-ID", "NAME"); + cli_printf("%16s %-24s\n", "LINK-ID", "NAME"); r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); while ((r = sd_bus_message_enter_container(m, 'e', "oa{sa{sv}}")) > 0) { r = sd_bus_message_read(m, "o", &obj); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); obj = shl_startswith(obj, "/org/freedesktop/miracle/link/"); if (!obj) { r = sd_bus_message_skip(m, "a{sa{sv}}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); continue; } free(link); link = sd_bus_label_unescape(obj); if (!link) - return log_ENOMEM(); + return cli_ENOMEM(); ++link_cnt; - r = verb_list_link(bus, m, link); + r = cmd_list_link(m, link); if (r < 0) return r; r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); - printf("\n"); + cli_printf("\n"); return link_cnt; } -static int verb_list_peer(sd_bus *bus, sd_bus_message *m, const char *peer) +static int cmd_list_peer(sd_bus_message *m, + const char *link_filter, + const char *peer) { - _cleanup_free_ char *link = NULL; + _shl_cleanup_free_ char *link = NULL; const char *obj, *name = ""; int r, connected = 0; r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); while ((r = sd_bus_message_enter_container(m, 'e', "sa{sv}")) > 0) { r = sd_bus_message_read(m, "s", &obj); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); if (strcmp(obj, "org.freedesktop.miracle.Peer")) { r = sd_bus_message_skip(m, "a{sv}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); continue; } r = sd_bus_message_enter_container(m, 'a', "{sv}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); while ((r = sd_bus_message_enter_container(m, 'e', "sv")) > 0) { r = sd_bus_message_read(m, "s", &obj); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); if (!strcmp(obj, "Link")) { r = bus_message_read_basic_variant(m, "o", &obj); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); obj = shl_startswith(obj, "/org/freedesktop/miracle/link/"); @@ -320,109 +636,113 @@ static int verb_list_peer(sd_bus *bus, sd_bus_message *m, const char *peer) free(link); link = sd_bus_label_unescape(obj); if (!link) - return log_ENOMEM(); + return cli_ENOMEM(); } } else if (!strcmp(obj, "Name")) { r = bus_message_read_basic_variant(m, "s", &name); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } else if (!strcmp(obj, "Connected")) { r = bus_message_read_basic_variant(m, "b", &connected); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } else { sd_bus_message_skip(m, "v"); } r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); - printf("%16s %-9s %-24s %-10s\n", - link ? : "", peer, name, connected ? "yes" : "no"); + if (!link_filter || !strcmp(link_filter, link)) + cli_printf("%16s %-9s %-24s %-10s\n", + link ? : "", + peer, + name, + connected ? "yes" : "no"); return 0; } -static int verb_list_peers(sd_bus *bus, sd_bus_message *m) +static int cmd_list_peers(sd_bus_message *m, const char *link_filter) { - _cleanup_free_ char *peer = NULL; + _shl_cleanup_free_ char *peer = NULL; unsigned int peer_cnt = 0; const char *obj; int r; - printf("%16s %-9s %-24s %-10s\n", - "LINK", "PEER-ID", "NAME", "CONNECTED"); + cli_printf("%16s %-9s %-24s %-10s\n", + "LINK", "PEER-ID", "NAME", "CONNECTED"); r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); while ((r = sd_bus_message_enter_container(m, 'e', "oa{sa{sv}}")) > 0) { r = sd_bus_message_read(m, "o", &obj); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); obj = shl_startswith(obj, "/org/freedesktop/miracle/peer/"); if (!obj) { r = sd_bus_message_skip(m, "a{sa{sv}}"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); continue; } free(peer); peer = sd_bus_label_unescape(obj); if (!peer) - return log_ENOMEM(); + return cli_ENOMEM(); ++peer_cnt; - r = verb_list_peer(bus, m, peer); + r = cmd_list_peer(m, link_filter, peer); if (r < 0) return r; r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); - printf("\n"); + cli_printf("\n"); return peer_cnt; } -static int verb_list(sd_bus *bus, char **args, unsigned int n) +static int cmd_list(char **args, unsigned int n) { _cleanup_sd_bus_message_ sd_bus_message *m = NULL; _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; @@ -438,14 +758,14 @@ static int verb_list(sd_bus *bus, char **args, unsigned int n) &m, ""); if (r < 0) { - log_error("cannot retrieve objects: %s", + cli_error("cannot retrieve objects: %s", bus_error_message(&err, r)); return r; } /* print links */ - r = verb_list_links(bus, m); + r = cmd_list_links(m); if (r < 0) return r; link_cnt = r; @@ -454,38 +774,94 @@ static int verb_list(sd_bus *bus, char **args, unsigned int n) /* print peers */ - r = verb_list_peers(bus, m); + r = cmd_list_peers(m, NULL); if (r < 0) return r; peer_cnt = r; /* print stats */ - printf(" %u peers and %u links listed.\n", peer_cnt, link_cnt); + cli_printf(" %u peers and %u links listed.\n", peer_cnt, link_cnt); return 0; } /* - * verb: show-link + * cmd: select */ -static int verb_show_link(sd_bus *bus, char **args, unsigned int n) +static int cmd_select(char **args, unsigned int n) { _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; - _cleanup_sd_bus_message_ sd_bus_message *m = NULL; - _cleanup_free_ char *path = NULL, *name = NULL; - _cleanup_free_ char *type = NULL, *iface = NULL, *fname = NULL; - const char *t; + _shl_cleanup_free_ char *path = NULL, *name = NULL; int r; - name = sd_bus_label_escape(args[1]); + if (!n) { + if (selected_link) { + cli_printf("link %s deselected\n", selected_link); + free(selected_link); + selected_link = NULL; + } + + return 0; + } + + name = sd_bus_label_escape(args[0]); if (!name) - return log_ENOMEM(); + return cli_ENOMEM(); path = shl_strcat("/org/freedesktop/miracle/link/", name); if (!path) - return log_ENOMEM(); + return cli_ENOMEM(); + + r = sd_bus_call_method(bus, + "org.freedesktop.miracle", + path, + "org.freedesktop.DBus.Properties", + "Get", + &err, + NULL, + "ss", "org.freedesktop.miracle.Link", "Type"); + if (r < 0) { + cli_error("unknown link %s: %s", + args[0], bus_error_message(&err, r)); + return r; + } + + free(selected_link); + selected_link = strdup(args[0]); + if (!selected_link) + return cli_ENOMEM(); + + cli_printf("link %s selected\n", selected_link); + return 0; +} + +/* + * cmd: show-link + */ + +static int cmd_show_link(char **args, unsigned int n) +{ + _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; + _cleanup_sd_bus_message_ sd_bus_message *m = NULL; + _shl_cleanup_free_ char *path = NULL, *name = NULL; + _shl_cleanup_free_ char *type = NULL, *iface = NULL, *fname = NULL; + const char *t, *arg_link; + int r; + + if (n > 0) + arg_link = args[0]; + else if (!(arg_link = selected_link)) + return log_error("no link selected"), -EINVAL; + + name = sd_bus_label_escape(arg_link); + if (!name) + return cli_ENOMEM(); + + path = shl_strcat("/org/freedesktop/miracle/link/", name); + if (!path) + return cli_ENOMEM(); r = sd_bus_call_method(bus, "org.freedesktop.miracle", @@ -496,96 +872,96 @@ static int verb_show_link(sd_bus *bus, char **args, unsigned int n) &m, "s", "org.freedesktop.miracle.Link"); if (r < 0) { - log_error("cannot retrieve link %s: %s", - args[1], bus_error_message(&err, r)); + cli_error("cannot retrieve link %s: %s", + arg_link, bus_error_message(&err, r)); return r; } r = sd_bus_message_enter_container(m, 'a', "{sv}"); if (r < 0) - return log_bus_parser(r); + 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 log_bus_parser(r); + return cli_log_parser(r); if (!strcmp(t, "Type")) { r = bus_message_read_basic_variant(m, "s", &t); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); free(type); type = strdup(t); if (!type) - return log_ENOMEM(); + return cli_ENOMEM(); } else if (!strcmp(t, "Interface")) { r = bus_message_read_basic_variant(m, "s", &t); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); free(iface); iface = strdup(t); if (!iface) - return log_ENOMEM(); + return cli_ENOMEM(); } else if (!strcmp(t, "Name")) { r = bus_message_read_basic_variant(m, "s", &t); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); free(fname); fname = strdup(t); if (!fname) - return log_ENOMEM(); + return cli_ENOMEM(); } else { r = sd_bus_message_skip(m, "v"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); - printf("Link=%s\n", args[1]); + cli_printf("Link=%s\n", arg_link); if (type) - printf("Type=%s\n", type); + cli_printf("Type=%s\n", type); if (iface) - printf("Interface=%s\n", iface); + cli_printf("Interface=%s\n", iface); if (fname) - printf("Name=%s\n", fname); + cli_printf("Name=%s\n", fname); return 0; } /* - * verb: show-peer + * cmd: show-peer */ -static int verb_show_peer(sd_bus *bus, char **args, unsigned int n) +static int cmd_show_peer(char **args, unsigned int n) { _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; _cleanup_sd_bus_message_ sd_bus_message *m = NULL; - _cleanup_free_ char *path = NULL, *name = NULL; - _cleanup_free_ char *link = NULL, *fname = NULL, *iface = NULL; - _cleanup_free_ char *laddr = NULL, *raddr = NULL; + _shl_cleanup_free_ char *path = NULL, *name = NULL; + _shl_cleanup_free_ char *link = NULL, *fname = NULL, *iface = NULL; + _shl_cleanup_free_ char *laddr = NULL, *raddr = NULL; const char *t; int r, is_connected = false; - name = sd_bus_label_escape(args[1]); + name = sd_bus_label_escape(args[0]); if (!name) - return log_ENOMEM(); + return cli_ENOMEM(); path = shl_strcat("/org/freedesktop/miracle/peer/", name); if (!path) - return log_ENOMEM(); + return cli_ENOMEM(); r = sd_bus_call_method(bus, "org.freedesktop.miracle", @@ -596,24 +972,24 @@ static int verb_show_peer(sd_bus *bus, char **args, unsigned int n) &m, "s", "org.freedesktop.miracle.Peer"); if (r < 0) { - log_error("cannot retrieve peer %s: %s", - args[1], bus_error_message(&err, r)); + cli_error("cannot retrieve peer %s: %s", + args[0], bus_error_message(&err, r)); return r; } r = sd_bus_message_enter_container(m, 'a', "{sv}"); if (r < 0) - return log_bus_parser(r); + 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 log_bus_parser(r); + return cli_log_parser(r); if (!strcmp(t, "Link")) { r = bus_message_read_basic_variant(m, "o", &t); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); t = shl_startswith(t, "/org/freedesktop/miracle/link/"); @@ -621,102 +997,102 @@ static int verb_show_peer(sd_bus *bus, char **args, unsigned int n) free(link); link = sd_bus_label_unescape(t); if (!link) - return log_ENOMEM(); + return cli_ENOMEM(); } } else if (!strcmp(t, "Name")) { r = bus_message_read_basic_variant(m, "s", &t); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); free(fname); fname = strdup(t); if (!fname) - return log_ENOMEM(); + return cli_ENOMEM(); } else if (!strcmp(t, "Connected")) { r = bus_message_read_basic_variant(m, "b", &is_connected); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } else if (!strcmp(t, "Interface")) { r = bus_message_read_basic_variant(m, "s", &t); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); free(iface); iface = strdup(t); if (!iface) - return log_ENOMEM(); + return cli_ENOMEM(); } else if (!strcmp(t, "LocalAddress")) { r = bus_message_read_basic_variant(m, "s", &t); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); free(laddr); laddr = strdup(t); if (!laddr) - return log_ENOMEM(); + return cli_ENOMEM(); } else if (!strcmp(t, "RemoteAddress")) { r = bus_message_read_basic_variant(m, "s", &t); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); free(raddr); raddr = strdup(t); if (!raddr) - return log_ENOMEM(); + return cli_ENOMEM(); } else { r = sd_bus_message_skip(m, "v"); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); } if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); r = sd_bus_message_exit_container(m); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); - printf("Peer=%s\n", args[1]); + cli_printf("Peer=%s\n", args[0]); if (link) - printf("Link=%s\n", link); + cli_printf("Link=%s\n", link); if (fname) - printf("Name=%s\n", fname); - printf("Connected=%d\n", is_connected); + cli_printf("Name=%s\n", fname); + cli_printf("Connected=%d\n", is_connected); if (iface) - printf("Interface=%s\n", iface); + cli_printf("Interface=%s\n", iface); if (laddr) - printf("LocalAddress=%s\n", laddr); + cli_printf("LocalAddress=%s\n", laddr); if (raddr) - printf("RemoteAddress=%s\n", raddr); + cli_printf("RemoteAddress=%s\n", raddr); return 0; } /* - * verb: add-link + * cmd: add-link */ -static int verb_add_link(sd_bus *bus, char **args, unsigned int n) +static int cmd_add_link(char **args, unsigned int n) { _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; _cleanup_sd_bus_message_ sd_bus_message *m = NULL; - _cleanup_free_ char *link = NULL, *type = NULL; + _shl_cleanup_free_ char *link = NULL, *type = NULL; const char *name; char *t, *iface; int r; - type = strdup(args[1]); + type = strdup(args[0]); if (!type) - return log_ENOMEM(); + return cli_ENOMEM(); t = strchr(type, ':'); if (!t) - return log_EINVAL(); + return cli_EINVAL(); *t = 0; iface = t + 1; @@ -730,28 +1106,28 @@ static int verb_add_link(sd_bus *bus, char **args, unsigned int n) &m, "ss", type, iface); if (r < 0) { - log_error("cannot add link %s:%s: %s", + cli_error("cannot add link %s:%s: %s", type, iface, bus_error_message(&err, r)); return r; } r = sd_bus_message_read(m, "s", &name); if (r < 0) - return log_bus_parser(r); + return cli_log_parser(r); link = sd_bus_label_unescape(name); if (!link) - return log_ENOMEM(); + return cli_ENOMEM(); - printf("Link added as %s\n", link); + cli_printf("link %s added\n", link); return 0; } /* - * verb: remove-link + * cmd: remove-link */ -static int verb_remove_link(sd_bus *bus, char **args, unsigned int n) +static int cmd_remove_link(char **args, unsigned int n) { _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; int r; @@ -763,35 +1139,45 @@ static int verb_remove_link(sd_bus *bus, char **args, unsigned int n) "RemoveLink", &err, NULL, - "s", args[1]); + "s", args[0]); if (r < 0) { - log_error("cannot remove link %s: %s", - args[1], bus_error_message(&err, r)); + cli_error("cannot remove link %s: %s", + args[0], bus_error_message(&err, r)); return r; } - printf("Link %s removed\n", args[1]); + cli_printf("link %s removed\n", args[0]); return 0; } /* - * verb: set-link-name + * cmd: set-link-name */ -static int verb_set_link_name(sd_bus *bus, char **args, unsigned int n) +static int cmd_set_link_name(char **args, unsigned int n) { _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; _cleanup_sd_bus_message_ sd_bus_message *m = NULL; - _cleanup_free_ char *path = NULL, *name = NULL; + _shl_cleanup_free_ char *path = NULL, *name = NULL; + const char *arg_link, *arg_name; int r; - name = sd_bus_label_escape(args[1]); + arg_link = args[0]; + arg_name = args[1]; + if (n < 2) { + if (!(arg_link = selected_link)) + return log_error("no link selected"), -EINVAL; + + arg_name = args[0]; + } + + name = sd_bus_label_escape(arg_link); if (!name) - return log_ENOMEM(); + return cli_ENOMEM(); path = shl_strcat("/org/freedesktop/miracle/link/", name); if (!path) - return log_ENOMEM(); + return cli_ENOMEM(); r = sd_bus_message_new_method_call(bus, "org.freedesktop.miracle", @@ -811,7 +1197,7 @@ static int verb_set_link_name(sd_bus *bus, char **args, unsigned int n) if (r < 0) return log_bus_create(r); - r = sd_bus_message_append(m, "s", args[2]); + r = sd_bus_message_append(m, "s", arg_name); if (r < 0) return log_bus_create(r); @@ -821,32 +1207,39 @@ static int verb_set_link_name(sd_bus *bus, char **args, unsigned int n) r = sd_bus_call(bus, m, 0, &err, NULL); if (r < 0) { - log_error("cannot set friendly-name to %s on link %s: %s", - args[2], args[1], bus_error_message(&err, r)); + cli_error("cannot set friendly-name to %s on link %s: %s", + arg_name, arg_link, bus_error_message(&err, r)); return r; } - printf("Friendly-name set to %s on link %s\n", args[2], args[1]); + cli_printf("Friendly-name set to %s on link %s\n", + arg_name, arg_link); return 0; } /* - * verb: start-scan + * cmd: start-scan */ -static int verb_start_scan(sd_bus *bus, char **args, unsigned int n) +static int cmd_start_scan(char **args, unsigned int n) { _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; - _cleanup_free_ char *path = NULL, *name = NULL; + _shl_cleanup_free_ char *path = NULL, *name = NULL; + const char *arg_link; int r; - name = sd_bus_label_escape(args[1]); + if (n > 0) + arg_link = args[0]; + else if (!(arg_link = selected_link)) + return log_error("no link selected"), -EINVAL; + + name = sd_bus_label_escape(arg_link); if (!name) - return log_ENOMEM(); + return cli_ENOMEM(); path = shl_strcat("/org/freedesktop/miracle/link/", name); if (!path) - return log_ENOMEM(); + return cli_ENOMEM(); r = sd_bus_call_method(bus, "org.freedesktop.miracle", @@ -857,32 +1250,38 @@ static int verb_start_scan(sd_bus *bus, char **args, unsigned int n) NULL, NULL); if (r < 0) { - log_error("cannot start scan on link %s: %s", - args[1], bus_error_message(&err, r)); + cli_error("cannot start scan on link %s: %s", + arg_link, bus_error_message(&err, r)); return r; } - printf("Scan on link %s started\n", args[1]); + cli_printf("Scan started on link %s\n", arg_link); return 0; } /* - * verb: stop-scan + * cmd: stop-scan */ -static int verb_stop_scan(sd_bus *bus, char **args, unsigned int n) +static int cmd_stop_scan(char **args, unsigned int n) { _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; - _cleanup_free_ char *path = NULL, *name = NULL; + _shl_cleanup_free_ char *path = NULL, *name = NULL; + const char *arg_link; int r; - name = sd_bus_label_escape(args[1]); + if (n > 0) + arg_link = args[0]; + else if (!(arg_link = selected_link)) + return log_error("no link selected"), -EINVAL; + + name = sd_bus_label_escape(arg_link); if (!name) - return log_ENOMEM(); + return cli_ENOMEM(); path = shl_strcat("/org/freedesktop/miracle/link/", name); if (!path) - return log_ENOMEM(); + return cli_ENOMEM(); r = sd_bus_call_method(bus, "org.freedesktop.miracle", @@ -893,118 +1292,61 @@ static int verb_stop_scan(sd_bus *bus, char **args, unsigned int n) NULL, NULL); if (r < 0) { - log_error("cannot stop scan on link %s: %s", - args[1], bus_error_message(&err, r)); + cli_error("cannot stop scan on link %s: %s", + arg_link, bus_error_message(&err, r)); return r; } - printf("Scan on link %s stopped\n", args[1]); + cli_printf("Scan stopped on link %s\n", arg_link); return 0; } /* - * verb: scan + * cmd: scan */ -static int verb_scan_list_peer(sd_bus *bus, - sd_bus_message *m, - const char *link_filter, - const char *peer) +static char *scan_link; + +static int cmd_scan_stop(void) { - const char *obj, *link = NULL, *name = NULL; - int r, connected = false; + _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; + _shl_cleanup_free_ char *path = NULL, *name = NULL; + int r; - r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); + if (!scan_link) + return 0; + + name = sd_bus_label_escape(scan_link); + if (!name) + return cli_ENOMEM(); + + path = shl_strcat("/org/freedesktop/miracle/link/", name); + if (!path) + return cli_ENOMEM(); + + r = sd_bus_call_method(bus, + "org.freedesktop.miracle", + path, + "org.freedesktop.miracle.Link", + "StopScan", + &err, + NULL, + NULL); if (r < 0) - return log_bus_parser(r); - - while ((r = sd_bus_message_enter_container(m, - 'e', - "sa{sv}")) > 0) { - r = sd_bus_message_read(m, "s", &obj); - if (r < 0) - return log_bus_parser(r); - - if (strcmp(obj, "org.freedesktop.miracle.Peer")) { - r = sd_bus_message_skip(m, "a{sv}"); - if (r < 0) - return log_bus_parser(r); - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_bus_parser(r); - continue; - } - - r = sd_bus_message_enter_container(m, 'a', "{sv}"); - if (r < 0) - return log_bus_parser(r); - - while ((r = sd_bus_message_enter_container(m, - 'e', - "sv")) > 0) { - r = sd_bus_message_read(m, "s", &obj); - if (r < 0) - return log_bus_parser(r); - - if (!strcmp(obj, "Link")) { - r = bus_message_read_basic_variant(m, "o", - &obj); - if (r < 0) - return log_bus_parser(r); - - obj = shl_startswith(obj, - "/org/freedesktop/miracle/link/"); - if (obj) - link = obj; - } else if (!strcmp(obj, "Name")) { - r = bus_message_read_basic_variant(m, "s", - &name); - if (r < 0) - return log_bus_parser(r); - } else if (!strcmp(obj, "Connected")) { - r = bus_message_read_basic_variant(m, "b", - &connected); - if (r < 0) - return log_bus_parser(r); - } else { - sd_bus_message_skip(m, "v"); - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_bus_parser(r); - } - if (r < 0) - return log_bus_parser(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_bus_parser(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_bus_parser(r); - } - if (r < 0) - return log_bus_parser(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_bus_parser(r); - - if (!strcmp(link, link_filter)) - printf("%4s %-24s %-10s\n", - peer, name, connected ? "yes" : "no"); + cli_error("cannot stop scan on link %s: %s", + scan_link, bus_error_message(&err, r)); + else + cli_printf("Scan stopped on link %s\n", scan_link); + free(scan_link); + scan_link = NULL; return 0; } -static int verb_scan_list(sd_bus *bus, const char *link) +static void cmd_scan_list(void) { _cleanup_sd_bus_message_ sd_bus_message *m = NULL; _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; - _cleanup_free_ char *peer = NULL; - const char *obj; int r; r = sd_bus_call_method(bus, @@ -1016,128 +1358,46 @@ static int verb_scan_list(sd_bus *bus, const char *link) &m, ""); if (r < 0) { - log_error("cannot retrieve objects: %s", + cli_error("cannot retrieve objects: %s", bus_error_message(&err, r)); - return r; + return; } - r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}"); - if (r < 0) - return log_bus_parser(r); - - while ((r = sd_bus_message_enter_container(m, - 'e', - "oa{sa{sv}}")) > 0) { - r = sd_bus_message_read(m, "o", &obj); - if (r < 0) - return log_bus_parser(r); - - obj = shl_startswith(obj, "/org/freedesktop/miracle/peer/"); - if (!obj) { - r = sd_bus_message_skip(m, "a{sa{sv}}"); - if (r < 0) - return log_bus_parser(r); - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_bus_parser(r); - continue; - } - - free(peer); - peer = sd_bus_label_unescape(obj); - if (!peer) - return log_ENOMEM(); - - r = verb_scan_list_peer(bus, m, link, peer); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_bus_parser(r); - } - if (r < 0) - return log_bus_parser(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_bus_parser(r); - - return 0; + cmd_list_peers(m, scan_link); } -static int verb_scan_match_fn(sd_bus *bus, - sd_bus_message *m, - void *data, - sd_bus_error *err) -{ - _cleanup_free_ char *peer = NULL; - const char *obj, *link = data; - int r; - - r = sd_bus_message_read(m, "o", &obj); - if (r < 0) - return log_bus_parser(r); - - obj = shl_startswith(obj, "/org/freedesktop/miracle/peer/"); - if (!obj) - return 0; - - peer = sd_bus_label_unescape(obj); - if (!peer) - return log_ENOMEM(); - - r = verb_scan_list_peer(bus, m, link, peer); - if (r < 0) - return r; - - return 0; -} - -static void verb_scan_listen(sd_bus *bus, const char *link) -{ - int r; - - r = sd_bus_add_match(bus, - "type='signal'," - "sender='org.freedesktop.miracle'," - "interface='org.freedesktop.DBus.ObjectManager'," - "member='InterfacesAdded'", - verb_scan_match_fn, - (void*)link); - if (r < 0) - return log_error("cannot add dbus match: %d", r); - - r = cli_init(bus); - if (r < 0) - goto error; - - verb_scan_list(bus, link); - cli_run(); - -error: - cli_destroy(); - sd_bus_remove_match(bus, - "type='signal'," - "sender='org.freedesktop.miracle'," - "interface='org.freedesktop.DBus.ObjectManager'", - verb_scan_match_fn, - NULL); -} - -static int verb_scan(sd_bus *bus, char **args, unsigned int n) +static int cmd_scan(char **args, unsigned int n) { _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; - _cleanup_free_ char *path = NULL, *name = NULL; + _shl_cleanup_free_ char *path = NULL, *name = NULL; + const char *arg_link; int r; - name = sd_bus_label_escape(args[1]); + if (n > 0 && !strcmp(args[0], "stop")) + return cmd_scan_stop(); + + if (scan_link) { + log_error("another managed scan is already running on link %s", + scan_link); + return -EINVAL; + } + + if (n > 0) + arg_link = args[0]; + else if (!(arg_link = selected_link)) + return log_error("no link selected"), -EINVAL; + + name = sd_bus_label_escape(arg_link); if (!name) - return log_ENOMEM(); + return cli_ENOMEM(); path = shl_strcat("/org/freedesktop/miracle/link/", name); if (!path) - return log_ENOMEM(); + return cli_ENOMEM(); + + scan_link = strdup(arg_link); + if (!scan_link) + return cli_ENOMEM(); r = sd_bus_call_method(bus, "org.freedesktop.miracle", @@ -1147,65 +1407,318 @@ static int verb_scan(sd_bus *bus, char **args, unsigned int n) &err, NULL, NULL); - if (r < 0) - log_warning("cannot start scan on link %s (already running?): %s", - args[1], bus_error_message(&err, r)); + if (r < 0) { + cli_warning("cannot start scan on link %s (already running?): %s", + arg_link, bus_error_message(&err, r)); + return -EINVAL; + } - printf("Scan on link %s started, listing peers..\n", args[1]); - printf("%4s %-24s %-10s\n", "ID", "NAME", "CONNECTED"); - verb_scan_listen(bus, name); + cli_printf("Scan started on link %s, listing peers..\n", arg_link); + cmd_scan_list(); + + return 0; +} + +/* + * cmd: quit/exit + */ + +static int cmd_quit(char **args, unsigned int n) +{ + sd_event_exit(cli_event, 0); + return 0; +} + +/* + * filters + */ + +static int filters_show_peer(const char *peer) +{ + _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; + _cleanup_sd_bus_message_ sd_bus_message *m = NULL; + _shl_cleanup_free_ char *path = NULL, *name = NULL; + _shl_cleanup_free_ char *link = NULL, *fname = NULL; + const char *t; + int r; + + name = sd_bus_label_escape(peer); + if (!name) + return cli_ENOMEM(); + + path = shl_strcat("/org/freedesktop/miracle/peer/", name); + if (!path) + return cli_ENOMEM(); r = sd_bus_call_method(bus, "org.freedesktop.miracle", path, - "org.freedesktop.miracle.Link", - "StopScan", + "org.freedesktop.DBus.Properties", + "GetAll", &err, - NULL, - NULL); + &m, + "s", "org.freedesktop.miracle.Peer"); + if (r < 0) { + cli_error("cannot retrieve peer %s: %s", + peer, bus_error_message(&err, r)); + return r; + } + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); if (r < 0) - log_error("cannot stop scan on link %s: %s", - args[1], bus_error_message(&err, r)); - else - printf("Scan on link %s stopped\n", args[1]); + 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, "Link")) { + r = bus_message_read_basic_variant(m, "o", &t); + if (r < 0) + return cli_log_parser(r); + + t = shl_startswith(t, + "/org/freedesktop/miracle/link/"); + if (t) { + free(link); + link = sd_bus_label_unescape(t); + if (!link) + return cli_ENOMEM(); + } + } else if (!strcmp(t, "Name")) { + r = bus_message_read_basic_variant(m, "s", &t); + if (r < 0) + return cli_log_parser(r); + + free(fname); + fname = strdup(t); + if (!fname) + return cli_ENOMEM(); + } else { + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return cli_log_parser(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); + + cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Peer %s@%s Name %s\n", + peer, link ? : "", fname ? : ""); return 0; } +static int filters_show_link(const char *link) +{ + _cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL; + _cleanup_sd_bus_message_ sd_bus_message *m = NULL; + _shl_cleanup_free_ char *path = NULL, *name = NULL, *fname = NULL; + const char *t; + int r; + + name = sd_bus_label_escape(link); + if (!name) + return cli_ENOMEM(); + + path = shl_strcat("/org/freedesktop/miracle/link/", name); + if (!path) + return cli_ENOMEM(); + + r = sd_bus_call_method(bus, + "org.freedesktop.miracle", + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &err, + &m, + "s", "org.freedesktop.miracle.Link"); + if (r < 0) { + cli_error("cannot retrieve link %s: %s", + link, bus_error_message(&err, r)); + return r; + } + + 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, "Name")) { + r = bus_message_read_basic_variant(m, "s", &t); + if (r < 0) + return cli_log_parser(r); + + free(fname); + fname = strdup(t); + if (!fname) + return cli_ENOMEM(); + } else { + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return cli_log_parser(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); + + cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Link %s Name %s\n", + link, fname ? : ""); + + return 0; +} + +static int filters_object_fn(sd_bus *bus, + sd_bus_message *m, + void *data, + sd_bus_error *err) +{ + _shl_cleanup_free_ char *peer = NULL, *link = NULL; + const char *obj, *t; + bool added; + int r; + + added = !strcmp(sd_bus_message_get_member(m), "InterfacesAdded"); + + r = sd_bus_message_read(m, "o", &obj); + if (r < 0) + return cli_log_parser(r); + + t = shl_startswith(obj, "/org/freedesktop/miracle/peer/"); + if (t) { + peer = sd_bus_label_unescape(t); + if (!peer) + return cli_ENOMEM(); + + if (added) { + r = filters_show_peer(peer); + if (r < 0) + return r; + } else { + cli_printf("[" CLI_YELLOW "REMOVE" CLI_DEFAULT "] Peer %s\n", + peer); + } + } + + t = shl_startswith(obj, "/org/freedesktop/miracle/link/"); + if (t) { + link = sd_bus_label_unescape(t); + if (!link) + return cli_ENOMEM(); + + if (added) { + r = filters_show_link(link); + if (r < 0) + return r; + } else { + cli_printf("[" CLI_YELLOW "REMOVE" CLI_DEFAULT "] Link %s\n", + link); + } + } + + return 0; +} + +static void filters_init() +{ + int r; + + r = sd_bus_add_match(bus, + "type='signal'," + "sender='org.freedesktop.miracle'," + "interface='org.freedesktop.DBus.ObjectManager'", + filters_object_fn, + NULL); + if (r < 0) + cli_error("cannot add dbus match: %d", r); +} + +static void filters_destroy() +{ + sd_bus_remove_match(bus, + "type='signal'," + "sender='org.freedesktop.miracle'," + "interface='org.freedesktop.DBus.ObjectManager'", + filters_object_fn, + NULL); +} + /* * main */ -static int help(void) -{ - /* - * 80-char barrier: - * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 - */ - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Send control command to or query the MiracleCast manager.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --log-level Maximum level for log messages\n" - " --log-time Prefix log-messages with timestamp\n" - "\n" - "Commands:\n" - " list List managed links and their peers\n" - " show-link LINK... Show details of given link\n" - " show-peer PEER... Show details of given peer\n" - " add-link LINK... Start managing the given link\n" - " remove-link LINK... Stop managing the given link\n" - " set-link-name LINK NAME Set friendly-name of given link\n" - " start-scan LINK... Start peer discovery on given link\n" - " stop-scan LINK... Stop peer discovery on given link\n" - " scan LINK... Interactive scan on given link\n" - , program_invocation_short_name); - /* - * 80-char barrier: - * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 - */ +static const struct cli_cmd cli_cmds[] = { + { "list", NULL, CLI_M, LESS, 0, cmd_list, "List links and peers" }, + { "select", "[link]", CLI_Y, LESS, 1, cmd_select, "Select default link" }, + { "show-link", "[link]", CLI_M, LESS, 1, cmd_show_link, "Show link information" }, + { "show-peer", "", CLI_M, EQUAL, 1, cmd_show_peer, "Show peer information" }, + { "add-link", "", CLI_M, EQUAL, 1, cmd_add_link, "Add link" }, + { "remove-link", "", CLI_M, EQUAL, 1, cmd_remove_link, "Remove link" }, + { "scan", "[link|stop]", CLI_Y, LESS, 1, cmd_scan, "Start/Stop managed scan" }, + { "start-scan", "[link]", CLI_N, LESS, 1, cmd_start_scan, "Start neighborhood scan" }, + { "stop-scan", "[link]", CLI_M, LESS, 1, cmd_stop_scan, "Stop neighborhood scan" }, + { "set-link-name", "[link] ", CLI_M, MORE, 1, cmd_set_link_name, "Set friendly name of link" }, + { "quit", NULL, CLI_Y, MORE, 0, cmd_quit, "Quit program" }, + { "exit", NULL, CLI_Y, MORE, 0, cmd_quit, NULL }, + { "help", NULL, CLI_M, MORE, 0, NULL, "Print help" }, + { }, +}; - return 0; +static int miraclectl_run(void) +{ + int r; + + filters_init(); + + r = cli_run(); + + cmd_scan_stop(); + filters_destroy(); + + return r; +} + +static int miraclectl_main(int argc, char *argv[]) +{ + int r, left; + + left = argc - optind; + if (left <= 0) { + r = cli_init(); + if (r < 0) + return r; + + r = miraclectl_run(); + cli_destroy(); + } else { + r = cli_do(argv + optind, left); + if (r == -EAGAIN) + cli_error("unknown operation %s", + argv[optind]); + } + + return r; } static int parse_argv(int argc, char *argv[]) @@ -1213,13 +1726,11 @@ static int parse_argv(int argc, char *argv[]) enum { ARG_VERSION = 0x100, ARG_LOG_LEVEL, - ARG_LOG_TIME, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-time", no_argument, NULL, ARG_LOG_TIME }, {} }; int c; @@ -1227,15 +1738,12 @@ static int parse_argv(int argc, char *argv[]) while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { switch (c) { case 'h': - return help(); + return cli_help(); case ARG_VERSION: puts(PACKAGE_STRING); return 0; case ARG_LOG_LEVEL: - log_max_sev = atoi(optarg); - break; - case ARG_LOG_TIME: - log_init_time(); + cli_max_sev = atoi(optarg); break; case '?': return -EINVAL; @@ -1245,81 +1753,8 @@ static int parse_argv(int argc, char *argv[]) return 1; } -static int miraclectl_main(sd_bus *bus, int argc, char *argv[]) -{ - static const struct { - const char *verb; - const enum { - MORE, - LESS, - EQUAL - } argc_cmp; - const int argc; - int (*dispatch) (sd_bus *bus, char **args, unsigned int n); - } verbs[] = { - { "list", LESS, 1, verb_list }, - { "show-link", EQUAL, 2, verb_show_link }, - { "show-peer", EQUAL, 2, verb_show_peer }, - { "add-link", EQUAL, 2, verb_add_link }, - { "remove-link", EQUAL, 2, verb_remove_link }, - { "set-link-name", EQUAL, 3, verb_set_link_name }, - { "start-scan", EQUAL, 2, verb_start_scan }, - { "stop-scan", EQUAL, 2, verb_stop_scan }, - { "scan", EQUAL, 2, verb_scan }, - }; - int left; - unsigned int i; - - left = argc - optind; - if (left <= 0) { - /* no argument means "list" */ - i = 0; - } else { - if (!strcmp(argv[optind], "help")) { - help(); - return 0; - } - - for (i = 0; i < SHL_ARRAY_LENGTH(verbs); i++) - if (!strcmp(argv[optind], verbs[i].verb)) - break; - - if (i >= SHL_ARRAY_LENGTH(verbs)) { - log_error("unknown operation %s", argv[optind]); - return -EINVAL; - } - } - - switch (verbs[i].argc_cmp) { - case EQUAL: - if (left != verbs[i].argc) { - log_error("invalid number of arguments"); - return -EINVAL; - } - - break; - case MORE: - if (left < verbs[i].argc) { - log_error("too few arguments"); - return -EINVAL; - } - - break; - case LESS: - if (left > verbs[i].argc) { - log_error("too many arguments"); - return -EINVAL; - } - - break; - } - - return verbs[i].dispatch(bus, argv + optind, left); -} - int main(int argc, char **argv) { - sd_bus *bus; int r; setlocale(LC_ALL, ""); @@ -1332,11 +1767,11 @@ int main(int argc, char **argv) r = sd_bus_default_system(&bus); if (r < 0) { - log_error("cannot connect to system bus: %s", strerror(-r)); + cli_error("cannot connect to system bus: %s", strerror(-r)); return EXIT_FAILURE; } - r = miraclectl_main(bus, argc, argv); + r = miraclectl_main(argc, argv); sd_bus_unref(bus); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;