584f56a233
When the ubus "call" command fails, ubus prints "Command failed: ...". This is fine when the call command is executed interactively by the user. However, when the call command is executed non-interactively, these log messages leave the user completely clueless: netifd: wan6 (10841): Command failed: Unknown error procd: /etc/rc.d/S80umdns: Failed to parse message data These messages contain absolutely no info that explains where they come from; it's not even clear they are coming from ubus. This makes tracking down what's causing them virtually impossible. Improve this situation by printing the full ubus call to stderr when stderr is not a TTY. Example of improved error message: netifd: wan (2836): Command failed: ubus call network.interface notify_proto { "action": 0, "link-up": false, "keep": false, "interface": "wan" } (Permission denied) While one might argue not to include the JSON message in the log, excluding it will still not help to reproduce the failed command. As we only print the full command when stderr is not a TTY, seeing this error means we're doing a bad ubus call somewhere, which is a bug and should be fixed. Printing the full command makes more sense than printing half the command and still requiring us to figure out the exact command that failed. Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be> Acked-by: Petr Štetiar <ynezz@true.cz> Acked-by: Jo-Philipp Wich <jo@mein.io>
672 lines
15 KiB
C
672 lines
15 KiB
C
/*
|
|
* Copyright (C) 2011 Felix Fietkau <nbd@openwrt.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License version 2.1
|
|
* as published by the Free Software Foundation
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <libubox/blobmsg_json.h>
|
|
#include "libubus.h"
|
|
|
|
static struct blob_buf b;
|
|
static int listen_timeout;
|
|
static int timeout = 30;
|
|
static bool simple_output = false;
|
|
static int verbose = 0;
|
|
static int monitor_dir = -1;
|
|
static uint32_t monitor_mask;
|
|
static const char * const monitor_types[] = {
|
|
[UBUS_MSG_HELLO] = "hello",
|
|
[UBUS_MSG_STATUS] = "status",
|
|
[UBUS_MSG_DATA] = "data",
|
|
[UBUS_MSG_PING] = "ping",
|
|
[UBUS_MSG_LOOKUP] = "lookup",
|
|
[UBUS_MSG_INVOKE] = "invoke",
|
|
[UBUS_MSG_ADD_OBJECT] = "add_object",
|
|
[UBUS_MSG_REMOVE_OBJECT] = "remove_object",
|
|
[UBUS_MSG_SUBSCRIBE] = "subscribe",
|
|
[UBUS_MSG_UNSUBSCRIBE] = "unsubscribe",
|
|
[UBUS_MSG_NOTIFY] = "notify",
|
|
};
|
|
|
|
static const char *format_type(void *priv, struct blob_attr *attr)
|
|
{
|
|
static const char * const attr_types[] = {
|
|
[BLOBMSG_TYPE_INT8] = "\"Boolean\"",
|
|
[BLOBMSG_TYPE_INT32] = "\"Integer\"",
|
|
[BLOBMSG_TYPE_STRING] = "\"String\"",
|
|
[BLOBMSG_TYPE_ARRAY] = "\"Array\"",
|
|
[BLOBMSG_TYPE_TABLE] = "\"Table\"",
|
|
};
|
|
const char *type = NULL;
|
|
size_t typeid;
|
|
|
|
if (blob_id(attr) != BLOBMSG_TYPE_INT32)
|
|
return NULL;
|
|
|
|
typeid = blobmsg_get_u32(attr);
|
|
if (typeid < ARRAY_SIZE(attr_types))
|
|
type = attr_types[typeid];
|
|
if (!type)
|
|
type = "\"(unknown)\"";
|
|
|
|
return type;
|
|
}
|
|
|
|
static void receive_list_result(struct ubus_context *ctx, struct ubus_object_data *obj, void *priv)
|
|
{
|
|
struct blob_attr *cur;
|
|
char *s;
|
|
size_t rem;
|
|
|
|
if (simple_output || !verbose) {
|
|
printf("%s\n", obj->path);
|
|
return;
|
|
}
|
|
|
|
printf("'%s' @%08x\n", obj->path, obj->id);
|
|
|
|
if (!obj->signature)
|
|
return;
|
|
|
|
blob_for_each_attr(cur, obj->signature, rem) {
|
|
s = blobmsg_format_json_with_cb(cur, false, format_type, NULL, -1);
|
|
printf("\t%s\n", s);
|
|
free(s);
|
|
}
|
|
}
|
|
|
|
static void receive_call_result_data(struct ubus_request *req, int type, struct blob_attr *msg)
|
|
{
|
|
char *str;
|
|
if (!msg)
|
|
return;
|
|
|
|
str = blobmsg_format_json_indent(msg, true, simple_output ? -1 : 0);
|
|
printf("%s\n", str);
|
|
free(str);
|
|
}
|
|
|
|
static void print_event(const char *type, struct blob_attr *msg)
|
|
{
|
|
char *str;
|
|
|
|
str = blobmsg_format_json(msg, true);
|
|
printf("{ \"%s\": %s }\n", type, str);
|
|
fflush(stdout);
|
|
free(str);
|
|
}
|
|
|
|
static int receive_request(struct ubus_context *ctx, struct ubus_object *obj,
|
|
struct ubus_request_data *req,
|
|
const char *method, struct blob_attr *msg)
|
|
{
|
|
print_event(method, msg);
|
|
return 0;
|
|
}
|
|
|
|
static void receive_event(struct ubus_context *ctx, struct ubus_event_handler *ev,
|
|
const char *type, struct blob_attr *msg)
|
|
{
|
|
print_event(type, msg);
|
|
}
|
|
|
|
static int ubus_cli_error(char *cmd, int argc, char **argv, int err)
|
|
{
|
|
int i;
|
|
|
|
if (!simple_output && !isatty(fileno(stderr))) {
|
|
fprintf(stderr, "Command failed: ubus %s ", cmd);
|
|
for (i = 0; i < argc; i++) {
|
|
fprintf(stderr, "%s ", argv[i]);
|
|
}
|
|
fprintf(stderr, "(%s)\n", ubus_strerror(err));
|
|
|
|
return -err;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ubus_cli_list(struct ubus_context *ctx, int argc, char **argv)
|
|
{
|
|
const char *path = NULL;
|
|
|
|
if (argc > 1)
|
|
return -2;
|
|
|
|
if (argc == 1)
|
|
path = argv[0];
|
|
|
|
return ubus_lookup(ctx, path, receive_list_result, NULL);
|
|
}
|
|
|
|
static int ubus_cli_call(struct ubus_context *ctx, int argc, char **argv)
|
|
{
|
|
uint32_t id;
|
|
int ret;
|
|
|
|
if (argc < 2 || argc > 3)
|
|
return -2;
|
|
|
|
blob_buf_init(&b, 0);
|
|
if (argc == 3 && !blobmsg_add_json_from_string(&b, argv[2])) {
|
|
return ubus_cli_error("call", argc, argv, UBUS_STATUS_PARSE_ERROR);
|
|
}
|
|
|
|
ret = ubus_lookup_id(ctx, argv[0], &id);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ubus_invoke(ctx, id, argv[1], b.head, receive_call_result_data, NULL, timeout * 1000);
|
|
if (ret)
|
|
return ubus_cli_error("call", argc, argv, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct cli_listen_data {
|
|
struct uloop_timeout timeout;
|
|
bool timed_out;
|
|
};
|
|
|
|
static void ubus_cli_listen_timeout(struct uloop_timeout *timeout)
|
|
{
|
|
struct cli_listen_data *data = container_of(timeout, struct cli_listen_data, timeout);
|
|
data->timed_out = true;
|
|
uloop_end();
|
|
}
|
|
|
|
static void do_listen(struct ubus_context *ctx, struct cli_listen_data *data)
|
|
{
|
|
memset(data, 0, sizeof(*data));
|
|
data->timeout.cb = ubus_cli_listen_timeout;
|
|
uloop_init();
|
|
ubus_add_uloop(ctx);
|
|
if (listen_timeout)
|
|
uloop_timeout_set(&data->timeout, listen_timeout * 1000);
|
|
uloop_run();
|
|
uloop_done();
|
|
}
|
|
|
|
static int ubus_cli_listen(struct ubus_context *ctx, int argc, char **argv)
|
|
{
|
|
struct ubus_event_handler ev = {
|
|
.cb = receive_event,
|
|
};
|
|
struct cli_listen_data data;
|
|
const char *event;
|
|
int ret = 0;
|
|
|
|
if (argc > 0) {
|
|
event = argv[0];
|
|
} else {
|
|
event = "*";
|
|
argc = 1;
|
|
}
|
|
|
|
do {
|
|
ret = ubus_register_event_handler(ctx, &ev, event);
|
|
if (ret)
|
|
break;
|
|
|
|
argv++;
|
|
argc--;
|
|
if (argc <= 0)
|
|
break;
|
|
|
|
event = argv[0];
|
|
} while (1);
|
|
|
|
if (ret) {
|
|
if (!simple_output)
|
|
fprintf(stderr, "Error while registering for event '%s': %s\n",
|
|
event, ubus_strerror(ret));
|
|
return -1;
|
|
}
|
|
|
|
do_listen(ctx, &data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ubus_cli_subscribe(struct ubus_context *ctx, int argc, char **argv)
|
|
{
|
|
struct ubus_subscriber sub = {
|
|
.cb = receive_request,
|
|
};
|
|
struct cli_listen_data data;
|
|
const char *event;
|
|
int ret = 0;
|
|
|
|
if (argc > 0) {
|
|
event = argv[0];
|
|
} else {
|
|
if (!simple_output)
|
|
fprintf(stderr, "You need to specify an object to subscribe to\n");
|
|
return -1;
|
|
}
|
|
|
|
ret = ubus_register_subscriber(ctx, &sub);
|
|
for (; !ret && argc > 0; argc--, argv++) {
|
|
uint32_t id;
|
|
|
|
ret = ubus_lookup_id(ctx, argv[0], &id);
|
|
if (ret)
|
|
break;
|
|
|
|
ret = ubus_subscribe(ctx, &sub, id);
|
|
}
|
|
|
|
if (ret) {
|
|
if (!simple_output)
|
|
fprintf(stderr, "Error while registering for event '%s': %s\n",
|
|
event, ubus_strerror(ret));
|
|
return -1;
|
|
}
|
|
|
|
do_listen(ctx, &data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int ubus_cli_send(struct ubus_context *ctx, int argc, char **argv)
|
|
{
|
|
if (argc < 1 || argc > 2)
|
|
return -2;
|
|
|
|
blob_buf_init(&b, 0);
|
|
|
|
if (argc == 2 && !blobmsg_add_json_from_string(&b, argv[1])) {
|
|
return UBUS_STATUS_PARSE_ERROR;
|
|
}
|
|
|
|
return ubus_send_event(ctx, argv[0], b.head);
|
|
}
|
|
|
|
struct cli_wait_data {
|
|
struct uloop_timeout timeout;
|
|
struct ubus_event_handler ev;
|
|
char **pending;
|
|
int n_pending;
|
|
};
|
|
|
|
static void wait_check_object(struct cli_wait_data *data, const char *path)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < data->n_pending; i++) {
|
|
if (strcmp(path, data->pending[i]) != 0)
|
|
continue;
|
|
|
|
data->n_pending--;
|
|
if (i == data->n_pending)
|
|
break;
|
|
|
|
memmove(&data->pending[i], &data->pending[i + 1],
|
|
(data->n_pending - i) * sizeof(*data->pending));
|
|
i--;
|
|
}
|
|
|
|
if (!data->n_pending)
|
|
uloop_end();
|
|
}
|
|
|
|
static void wait_event_cb(struct ubus_context *ctx, struct ubus_event_handler *ev,
|
|
const char *type, struct blob_attr *msg)
|
|
{
|
|
static const struct blobmsg_policy policy = {
|
|
"path", BLOBMSG_TYPE_STRING
|
|
};
|
|
struct cli_wait_data *data = container_of(ev, struct cli_wait_data, ev);
|
|
struct blob_attr *attr;
|
|
const char *path;
|
|
|
|
if (strcmp(type, "ubus.object.add") != 0)
|
|
return;
|
|
|
|
blobmsg_parse(&policy, 1, &attr, blob_data(msg), blob_len(msg));
|
|
if (!attr)
|
|
return;
|
|
|
|
path = blobmsg_data(attr);
|
|
wait_check_object(data, path);
|
|
}
|
|
|
|
static void wait_list_cb(struct ubus_context *ctx, struct ubus_object_data *obj, void *priv)
|
|
{
|
|
struct cli_wait_data *data = priv;
|
|
|
|
wait_check_object(data, obj->path);
|
|
}
|
|
|
|
|
|
static void wait_timeout(struct uloop_timeout *timeout)
|
|
{
|
|
uloop_end();
|
|
}
|
|
|
|
static int ubus_cli_wait_for(struct ubus_context *ctx, int argc, char **argv)
|
|
{
|
|
struct cli_wait_data data = {
|
|
.timeout.cb = wait_timeout,
|
|
.ev.cb = wait_event_cb,
|
|
.pending = argv,
|
|
.n_pending = argc,
|
|
};
|
|
int ret;
|
|
|
|
if (argc < 1)
|
|
return -2;
|
|
|
|
uloop_init();
|
|
ubus_add_uloop(ctx);
|
|
|
|
ret = ubus_register_event_handler(ctx, &data.ev, "ubus.object.add");
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!data.n_pending)
|
|
return ret;
|
|
|
|
ret = ubus_lookup(ctx, NULL, wait_list_cb, &data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!data.n_pending)
|
|
return ret;
|
|
|
|
uloop_timeout_set(&data.timeout, timeout * 1000);
|
|
uloop_run();
|
|
uloop_done();
|
|
|
|
if (data.n_pending)
|
|
return UBUS_STATUS_TIMEOUT;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const char *
|
|
ubus_cli_msg_type(uint32_t type)
|
|
{
|
|
const char *ret = NULL;
|
|
static char unk_type[16];
|
|
|
|
|
|
if (type < ARRAY_SIZE(monitor_types))
|
|
ret = monitor_types[type];
|
|
|
|
if (!ret) {
|
|
snprintf(unk_type, sizeof(unk_type), "%d", type);
|
|
ret = unk_type;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
ubus_cli_get_monitor_data(struct blob_attr *data)
|
|
{
|
|
static const struct blob_attr_info policy[UBUS_ATTR_MAX] = {
|
|
[UBUS_ATTR_STATUS] = { .type = BLOB_ATTR_INT32 },
|
|
[UBUS_ATTR_OBJPATH] = { .type = BLOB_ATTR_STRING },
|
|
[UBUS_ATTR_OBJID] = { .type = BLOB_ATTR_INT32 },
|
|
[UBUS_ATTR_METHOD] = { .type = BLOB_ATTR_STRING },
|
|
[UBUS_ATTR_OBJTYPE] = { .type = BLOB_ATTR_INT32 },
|
|
[UBUS_ATTR_SIGNATURE] = { .type = BLOB_ATTR_NESTED },
|
|
[UBUS_ATTR_DATA] = { .type = BLOB_ATTR_NESTED },
|
|
[UBUS_ATTR_ACTIVE] = { .type = BLOB_ATTR_INT8 },
|
|
[UBUS_ATTR_NO_REPLY] = { .type = BLOB_ATTR_INT8 },
|
|
[UBUS_ATTR_USER] = { .type = BLOB_ATTR_STRING },
|
|
[UBUS_ATTR_GROUP] = { .type = BLOB_ATTR_STRING },
|
|
};
|
|
static const char * const names[UBUS_ATTR_MAX] = {
|
|
[UBUS_ATTR_STATUS] = "status",
|
|
[UBUS_ATTR_OBJPATH] = "objpath",
|
|
[UBUS_ATTR_OBJID] = "objid",
|
|
[UBUS_ATTR_METHOD] = "method",
|
|
[UBUS_ATTR_OBJTYPE] = "objtype",
|
|
[UBUS_ATTR_SIGNATURE] = "signature",
|
|
[UBUS_ATTR_DATA] = "data",
|
|
[UBUS_ATTR_ACTIVE] = "active",
|
|
[UBUS_ATTR_NO_REPLY] = "no_reply",
|
|
[UBUS_ATTR_USER] = "user",
|
|
[UBUS_ATTR_GROUP] = "group",
|
|
};
|
|
struct blob_attr *tb[UBUS_ATTR_MAX];
|
|
int i;
|
|
|
|
blob_buf_init(&b, 0);
|
|
blob_parse(data, tb, policy, UBUS_ATTR_MAX);
|
|
|
|
for (i = 0; i < UBUS_ATTR_MAX; i++) {
|
|
const char *n = names[i];
|
|
struct blob_attr *v = tb[i];
|
|
|
|
if (!tb[i] || !n)
|
|
continue;
|
|
|
|
switch(policy[i].type) {
|
|
case BLOB_ATTR_INT32:
|
|
blobmsg_add_u32(&b, n, blob_get_int32(v));
|
|
break;
|
|
case BLOB_ATTR_STRING:
|
|
blobmsg_add_string(&b, n, blob_data(v));
|
|
break;
|
|
case BLOB_ATTR_INT8:
|
|
blobmsg_add_u8(&b, n, !!blob_get_int8(v));
|
|
break;
|
|
case BLOB_ATTR_NESTED:
|
|
blobmsg_add_field(&b, BLOBMSG_TYPE_TABLE, n, blobmsg_data(v), blobmsg_data_len(v));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return blobmsg_format_json(b.head, true);
|
|
}
|
|
|
|
static void
|
|
ubus_cli_monitor_cb(struct ubus_context *ctx, uint32_t seq, struct blob_attr *msg)
|
|
{
|
|
static const struct blob_attr_info policy[UBUS_MONITOR_MAX] = {
|
|
[UBUS_MONITOR_CLIENT] = { .type = BLOB_ATTR_INT32 },
|
|
[UBUS_MONITOR_PEER] = { .type = BLOB_ATTR_INT32 },
|
|
[UBUS_MONITOR_SEND] = { .type = BLOB_ATTR_INT8 },
|
|
[UBUS_MONITOR_TYPE] = { .type = BLOB_ATTR_INT32 },
|
|
[UBUS_MONITOR_DATA] = { .type = BLOB_ATTR_NESTED },
|
|
};
|
|
struct blob_attr *tb[UBUS_MONITOR_MAX];
|
|
uint32_t client, peer, type;
|
|
bool send;
|
|
char *data;
|
|
|
|
blob_parse_untrusted(msg, blob_raw_len(msg), tb, policy, UBUS_MONITOR_MAX);
|
|
|
|
if (!tb[UBUS_MONITOR_CLIENT] ||
|
|
!tb[UBUS_MONITOR_PEER] ||
|
|
!tb[UBUS_MONITOR_SEND] ||
|
|
!tb[UBUS_MONITOR_TYPE] ||
|
|
!tb[UBUS_MONITOR_DATA]) {
|
|
printf("Invalid monitor msg\n");
|
|
return;
|
|
}
|
|
|
|
send = blob_get_int32(tb[UBUS_MONITOR_SEND]);
|
|
client = blob_get_int32(tb[UBUS_MONITOR_CLIENT]);
|
|
peer = blob_get_int32(tb[UBUS_MONITOR_PEER]);
|
|
type = blob_get_int32(tb[UBUS_MONITOR_TYPE]);
|
|
|
|
if (monitor_mask && type < 32 && !(monitor_mask & (1 << type)))
|
|
return;
|
|
|
|
if (monitor_dir >= 0 && send != monitor_dir)
|
|
return;
|
|
|
|
data = ubus_cli_get_monitor_data(tb[UBUS_MONITOR_DATA]);
|
|
printf("%s %08x #%08x %14s: %s\n", send ? "->" : "<-", client, peer, ubus_cli_msg_type(type), data);
|
|
free(data);
|
|
fflush(stdout);
|
|
}
|
|
|
|
static int ubus_cli_monitor(struct ubus_context *ctx, int argc, char **argv)
|
|
{
|
|
int ret;
|
|
|
|
uloop_init();
|
|
ubus_add_uloop(ctx);
|
|
ctx->monitor_cb = ubus_cli_monitor_cb;
|
|
ret = ubus_monitor_start(ctx);
|
|
if (ret)
|
|
return ret;
|
|
|
|
uloop_run();
|
|
uloop_done();
|
|
|
|
ubus_monitor_stop(ctx);
|
|
return 0;
|
|
}
|
|
|
|
static int add_monitor_type(const char *type)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(monitor_types); i++) {
|
|
if (!monitor_types[i] || strcmp(monitor_types[i], type) != 0)
|
|
continue;
|
|
|
|
monitor_mask |= 1 << i;
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int usage(const char *prog)
|
|
{
|
|
fprintf(stderr,
|
|
"Usage: %s [<options>] <command> [arguments...]\n"
|
|
"Options:\n"
|
|
" -s <socket>: Set the unix domain socket to connect to\n"
|
|
" -t <timeout>: Set the timeout (in seconds) for a command to complete\n"
|
|
" -S: Use simplified output (for scripts)\n"
|
|
" -v: More verbose output\n"
|
|
" -m <type>: (for monitor): include a specific message type\n"
|
|
" (can be used more than once)\n"
|
|
" -M <r|t> (for monitor): only capture received or transmitted traffic\n"
|
|
"\n"
|
|
"Commands:\n"
|
|
" - list [<path>] List objects\n"
|
|
" - call <path> <method> [<message>] Call an object method\n"
|
|
" - subscribe <path> [<path>...] Subscribe to object(s) notifications\n"
|
|
" - listen [<path>...] Listen for events\n"
|
|
" - send <type> [<message>] Send an event\n"
|
|
" - wait_for <object> [<object>...] Wait for multiple objects to appear on ubus\n"
|
|
" - monitor Monitor ubus traffic\n"
|
|
"\n", prog);
|
|
return 1;
|
|
}
|
|
|
|
|
|
static struct {
|
|
const char *name;
|
|
int (*cb)(struct ubus_context *ctx, int argc, char **argv);
|
|
} commands[] = {
|
|
{ "list", ubus_cli_list },
|
|
{ "call", ubus_cli_call },
|
|
{ "listen", ubus_cli_listen },
|
|
{ "subscribe", ubus_cli_subscribe },
|
|
{ "send", ubus_cli_send },
|
|
{ "wait_for", ubus_cli_wait_for },
|
|
{ "monitor", ubus_cli_monitor },
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *progname, *ubus_socket = NULL;
|
|
struct ubus_context *ctx;
|
|
int ret = 0;
|
|
char *cmd;
|
|
size_t i;
|
|
int ch;
|
|
|
|
progname = argv[0];
|
|
|
|
while ((ch = getopt(argc, argv, "m:M:vs:t:S")) != -1) {
|
|
switch (ch) {
|
|
case 's':
|
|
ubus_socket = optarg;
|
|
break;
|
|
case 't':
|
|
listen_timeout = atoi(optarg);
|
|
timeout = atoi(optarg);
|
|
break;
|
|
case 'S':
|
|
simple_output = true;
|
|
break;
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
case 'm':
|
|
if (add_monitor_type(optarg))
|
|
return usage(progname);
|
|
break;
|
|
case 'M':
|
|
switch (optarg[0]) {
|
|
case 'r':
|
|
monitor_dir = 0;
|
|
break;
|
|
case 't':
|
|
monitor_dir = 1;
|
|
break;
|
|
default:
|
|
return usage(progname);
|
|
}
|
|
break;
|
|
default:
|
|
return usage(progname);
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
cmd = argv[0];
|
|
if (argc < 1)
|
|
return usage(progname);
|
|
|
|
ctx = ubus_connect(ubus_socket);
|
|
if (!ctx) {
|
|
if (!simple_output)
|
|
fprintf(stderr, "Failed to connect to ubus\n");
|
|
return -1;
|
|
}
|
|
|
|
argv++;
|
|
argc--;
|
|
|
|
ret = -2;
|
|
for (i = 0; i < ARRAY_SIZE(commands); i++) {
|
|
if (strcmp(commands[i].name, cmd) != 0)
|
|
continue;
|
|
|
|
ret = commands[i].cb(ctx, argc, argv);
|
|
break;
|
|
}
|
|
|
|
if (ret > 0 && !simple_output)
|
|
fprintf(stderr, "Command failed: %s\n", ubus_strerror(ret));
|
|
else if (ret == -2)
|
|
usage(progname);
|
|
|
|
ubus_free(ctx);
|
|
return ret;
|
|
}
|