123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- /*
- * Copyright (C) 2013-2014 Jo-Philipp Wich <jo@mein.io>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
- #include <stdio.h>
- #include <stdbool.h>
- #include <stdint.h>
- #include <unistd.h>
- #include <errno.h>
- #ifdef JSONC
- #include <json.h>
- #else
- #include <json-c/json.h>
- #endif
- #include <libubox/list.h>
- #include "lexer.h"
- #include "parser.h"
- #include "matcher.h"
- struct match_item {
- struct json_object *jsobj;
- struct list_head list;
- };
- static void
- print_usage(char *app)
- {
- printf(
- "== Usage ==\n\n"
- " # %s [-a] [-i <file> | -s \"json...\"] {-t <pattern> | -e <pattern>}\n"
- " -q Quiet, no errors are printed\n"
- " -h, --help Print this help\n"
- " -a Implicitely treat input as array, useful for JSON logs\n"
- " -i path Specify a JSON file to parse\n"
- " -s \"json\" Specify a JSON string to parse\n"
- " -l limit Specify max number of results to show\n"
- " -F separator Specify a field separator when using export\n"
- " -t <pattern> Print the type of values matched by pattern\n"
- " -e <pattern> Print the values matched by pattern\n"
- " -e VAR=<pat> Serialize matched value for shell \"eval\"\n\n"
- "== Patterns ==\n\n"
- " Patterns are JsonPath: http://goessner.net/articles/JsonPath/\n"
- " This tool implements $, @, [], * and the union operator ','\n"
- " plus the usual expressions and literals.\n"
- " It does not support the recursive child search operator '..' or\n"
- " the '?()' and '()' filter expressions as those would require a\n"
- " complete JavaScript engine to support them.\n\n"
- "== Examples ==\n\n"
- " Display the first IPv4 address on lan:\n"
- " # ifstatus lan | %s -e '@[\"ipv4-address\"][0].address'\n\n"
- " Extract the release string from the board information:\n"
- " # ubus call system board | %s -e '@.release.description'\n\n"
- " Find all interfaces which are up:\n"
- " # ubus call network.interface dump | \\\n"
- " %s -e '@.interface[@.up=true].interface'\n\n"
- " Export br-lan traffic counters for shell eval:\n"
- " # devstatus br-lan | %s -e 'RX=@.statistics.rx_bytes' \\\n"
- " -e 'TX=@.statistics.tx_bytes'\n",
- app, app, app, app, app);
- }
- static struct json_object *
- parse_json_chunk(struct json_tokener *tok, struct json_object *array,
- const char *buf, size_t len, enum json_tokener_error *err)
- {
- struct json_object *obj = NULL;
- while (len)
- {
- obj = json_tokener_parse_ex(tok, buf, len);
- *err = json_tokener_get_error(tok);
- if (*err == json_tokener_success)
- {
- if (array)
- {
- json_object_array_add(array, obj);
- }
- else
- {
- break;
- }
- }
- else if (*err != json_tokener_continue)
- {
- break;
- }
- buf += tok->char_offset;
- len -= tok->char_offset;
- }
- return obj;
- }
- static struct json_object *
- parse_json(FILE *fd, const char *source, const char **error, bool array_mode)
- {
- size_t len;
- char buf[256];
- struct json_object *obj = NULL, *array = NULL;
- struct json_tokener *tok = json_tokener_new();
- enum json_tokener_error err = json_tokener_continue;
- if (!tok)
- {
- *error = "Out of memory";
- return NULL;
- }
- if (array_mode)
- {
- array = json_object_new_array();
- if (!array)
- {
- json_tokener_free(tok);
- *error = "Out of memory";
- return NULL;
- }
- }
- if (source)
- {
- obj = parse_json_chunk(tok, array, source, strlen(source), &err);
- }
- else
- {
- while ((len = fread(buf, 1, sizeof(buf), fd)) > 0)
- {
- obj = parse_json_chunk(tok, array, buf, len, &err);
- if ((err == json_tokener_success && array_mode == false) ||
- (err != json_tokener_continue && err != json_tokener_success))
- break;
- }
- }
- json_tokener_free(tok);
- if (err)
- {
- if (err == json_tokener_continue)
- err = json_tokener_error_parse_eof;
- *error = json_tokener_error_desc(err);
- return NULL;
- }
- return array ? array : obj;
- }
- static void
- print_string(const char *s)
- {
- const char *p;
- printf("'");
- for (p = s; *p; p++)
- {
- if (*p == '\'')
- printf("'\"'\"'");
- else
- printf("%c", *p);
- }
- printf("'");
- }
- static void
- print_separator(const char *sep, int *sc, int sl)
- {
- if (*sc > 0)
- {
- switch (sep[(*sc - 1) % sl])
- {
- case '"':
- printf("'\"'");
- break;
- case '\'':
- printf("\"'\"");
- break;
- case ' ':
- printf("\\ ");
- break;
- default:
- printf("%c", sep[(*sc - 1) % sl]);
- }
- }
- (*sc)++;
- }
- static void
- export_value(struct list_head *matches, const char *prefix, const char *sep,
- int limit)
- {
- int n, len;
- int sc = 0, sl = strlen(sep);
- struct match_item *item;
- if (list_empty(matches))
- return;
- if (prefix)
- {
- printf("export %s=", prefix);
- list_for_each_entry(item, matches, list)
- {
- if (limit-- <= 0)
- break;
- switch (json_object_get_type(item->jsobj))
- {
- case json_type_object:
- ; /* a label can only be part of a statement */
- json_object_object_foreach(item->jsobj, key, val)
- {
- if (!val)
- continue;
- print_separator(sep, &sc, sl);
- print_string(key);
- }
- break;
- case json_type_array:
- for (n = 0, len = json_object_array_length(item->jsobj);
- n < len; n++)
- {
- print_separator(sep, &sc, sl);
- printf("%d", n);
- }
- break;
- case json_type_boolean:
- print_separator(sep, &sc, sl);
- printf("%d", json_object_get_boolean(item->jsobj));
- break;
- case json_type_int:
- print_separator(sep, &sc, sl);
- printf("%" PRId64, json_object_get_int64(item->jsobj));
- break;
- case json_type_double:
- print_separator(sep, &sc, sl);
- printf("%f", json_object_get_double(item->jsobj));
- break;
- case json_type_string:
- print_separator(sep, &sc, sl);
- print_string(json_object_get_string(item->jsobj));
- break;
- case json_type_null:
- break;
- }
- }
- printf("; ");
- }
- else
- {
- list_for_each_entry(item, matches, list)
- {
- if (limit-- <= 0)
- break;
- switch (json_object_get_type(item->jsobj))
- {
- case json_type_object:
- case json_type_array:
- case json_type_boolean:
- case json_type_int:
- case json_type_double:
- printf("%s\n", json_object_to_json_string(item->jsobj));
- break;
- case json_type_string:
- printf("%s\n", json_object_get_string(item->jsobj));
- break;
- case json_type_null:
- break;
- }
- }
- }
- }
- static void
- export_type(struct list_head *matches, const char *prefix, int limit)
- {
- bool first = true;
- struct match_item *item;
- const char *types[] = {
- "null",
- "boolean",
- "double",
- "int",
- "object",
- "array",
- "string"
- };
- if (list_empty(matches))
- return;
- if (prefix)
- printf("export %s=", prefix);
- list_for_each_entry(item, matches, list)
- {
- if (!first)
- printf("\\ ");
- if (limit-- <= 0)
- break;
- printf("%s", types[json_object_get_type(item->jsobj)]);
- first = false;
- }
- if (prefix)
- printf("; ");
- else
- printf("\n");
- }
- static void
- match_cb(struct json_object *res, void *priv)
- {
- struct list_head *h = priv;
- struct match_item *i = calloc(1, sizeof(*i));
- if (i)
- {
- i->jsobj = res;
- list_add_tail(&i->list, h);
- }
- }
- static void
- print_error(struct jp_state *state, char *expr)
- {
- int i;
- bool first = true;
- fprintf(stderr, "Syntax error: ");
- switch (state->error_code)
- {
- case -4:
- fprintf(stderr, "Unexpected character\n");
- break;
- case -3:
- fprintf(stderr, "String or label literal too long\n");
- break;
- case -2:
- fprintf(stderr, "Invalid escape sequence\n");
- break;
- case -1:
- fprintf(stderr, "Unterminated string\n");
- break;
- default:
- for (i = 0; i < sizeof(state->error_code) * 8; i++)
- {
- if (state->error_code & (1 << i))
- {
- fprintf(stderr,
- first ? "Expecting %s" : " or %s", tokennames[i]);
- first = false;
- }
- }
- fprintf(stderr, "\n");
- break;
- }
- fprintf(stderr, "In expression %s\n", expr);
- fprintf(stderr, "Near here ----");
- for (i = 0; i < state->error_pos; i++)
- fprintf(stderr, "-");
- fprintf(stderr, "^\n");
- }
- static bool
- filter_json(int opt, struct json_object *jsobj, char *expr, const char *sep,
- int limit)
- {
- struct jp_state *state;
- const char *prefix = NULL;
- struct list_head matches;
- struct match_item *item, *tmp;
- struct json_object *res = NULL;
- state = jp_parse(expr);
- if (!state)
- {
- fprintf(stderr, "Out of memory\n");
- goto out;
- }
- else if (state->error_code)
- {
- print_error(state, expr);
- goto out;
- }
- INIT_LIST_HEAD(&matches);
- res = jp_match(state->path, jsobj, match_cb, &matches);
- prefix = (state->path->type == T_LABEL) ? state->path->str : NULL;
- switch (opt)
- {
- case 't':
- export_type(&matches, prefix, limit);
- break;
- default:
- export_value(&matches, prefix, sep, limit);
- break;
- }
- list_for_each_entry_safe(item, tmp, &matches, list)
- free(item);
- out:
- if (state)
- jp_free(state);
- return !!res;
- }
- int main(int argc, char **argv)
- {
- bool array_mode = false;
- int opt, rv = 0, limit = 0x7FFFFFFF;
- FILE *input = stdin;
- struct json_object *jsobj = NULL;
- const char *jserr = NULL, *source = NULL, *separator = " ";
- if (argc == 1)
- {
- print_usage(argv[0]);
- goto out;
- }
- while ((opt = getopt(argc, argv, "ahi:s:e:t:F:l:q")) != -1)
- {
- switch (opt)
- {
- case 'a':
- array_mode = true;
- break;
- case 'h':
- print_usage(argv[0]);
- goto out;
- case 'i':
- input = fopen(optarg, "r");
- if (!input)
- {
- fprintf(stderr, "Failed to open %s: %s\n",
- optarg, strerror(errno));
- rv = 125;
- goto out;
- }
- break;
- case 's':
- source = optarg;
- break;
- case 'F':
- if (optarg && *optarg)
- separator = optarg;
- break;
- case 'l':
- limit = atoi(optarg);
- break;
- case 't':
- case 'e':
- if (!jsobj)
- {
- jsobj = parse_json(input, source, &jserr, array_mode);
- if (!jsobj)
- {
- fprintf(stderr, "Failed to parse json data: %s\n",
- jserr);
- rv = 126;
- goto out;
- }
- }
- if (!filter_json(opt, jsobj, optarg, separator, limit))
- rv = 1;
- break;
- case 'q':
- fclose(stderr);
- break;
- }
- }
- out:
- if (jsobj)
- json_object_put(jsobj);
- if (input && input != stdin)
- fclose(input);
- return rv;
- }
|