Add tests for the simple json parser.

This commit is contained in:
Todd C. Miller
2020-03-29 05:05:08 -06:00
parent cffda82e20
commit 3cd9cbbadf
10 changed files with 563 additions and 53 deletions

View File

@@ -100,8 +100,14 @@ lib/iolog/Makefile.in
lib/iolog/hostcheck.c lib/iolog/hostcheck.c
lib/iolog/iolog_fileio.c lib/iolog/iolog_fileio.c
lib/iolog/iolog_json.c lib/iolog/iolog_json.c
lib/iolog/iolog_json.h
lib/iolog/iolog_path.c lib/iolog/iolog_path.c
lib/iolog/iolog_util.c lib/iolog/iolog_util.c
lib/iolog/regress/iolog_json/check_iolog_json.c
lib/iolog/regress/iolog_json/test1.in
lib/iolog/regress/iolog_json/test2.in
lib/iolog/regress/iolog_json/test2.out.ok
lib/iolog/regress/iolog_json/test3.in
lib/iolog/regress/iolog_path/check_iolog_path.c lib/iolog/regress/iolog_path/check_iolog_path.c
lib/iolog/regress/iolog_path/data lib/iolog/regress/iolog_path/data
lib/iolog/regress/iolog_util/check_iolog_util.c lib/iolog/regress/iolog_util/check_iolog_util.c

View File

@@ -75,7 +75,7 @@ PVS_IGNORE = 'V707,V011,V002,V536'
PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE) PVS_LOG_OPTS = -a 'GA:1,2' -e -t errorfile -d $(PVS_IGNORE)
# Regression tests # Regression tests
TEST_PROGS = check_iolog_path check_iolog_util TEST_PROGS = check_iolog_json check_iolog_path check_iolog_util
TEST_LIBS = @LIBS@ TEST_LIBS = @LIBS@
TEST_LDFLAGS = @LDFLAGS@ TEST_LDFLAGS = @LDFLAGS@
@@ -97,6 +97,8 @@ CHECK_IOLOG_PATH_OBJS = check_iolog_path.lo iolog_path.lo
CHECK_IOLOG_UTIL_OBJS = check_iolog_util.lo iolog_json.lo iolog_util.lo CHECK_IOLOG_UTIL_OBJS = check_iolog_util.lo iolog_json.lo iolog_util.lo
CHECK_IOLOG_JSON_OBJS = check_iolog_json.lo iolog_json.lo
all: libsudo_iolog.la all: libsudo_iolog.la
pvs-log-files: $(POBJS) pvs-log-files: $(POBJS)
@@ -132,6 +134,9 @@ check_iolog_path: $(CHECK_IOLOG_PATH_OBJS) libsudo_iolog.la
check_iolog_util: $(CHECK_IOLOG_UTIL_OBJS) libsudo_iolog.la check_iolog_util: $(CHECK_IOLOG_UTIL_OBJS) libsudo_iolog.la
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_UTIL_OBJS) libsudo_iolog.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS) $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_UTIL_OBJS) libsudo_iolog.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
check_iolog_json: $(CHECK_IOLOG_JSON_OBJS) libsudo_iolog.la
$(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_JSON_OBJS) libsudo_iolog.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS)
pre-install: pre-install:
install: install:
@@ -159,6 +164,7 @@ check: $(TEST_PROGS)
LC_ALL=C; export LC_ALL; \ LC_ALL=C; export LC_ALL; \
unset LANG || LANG=; \ unset LANG || LANG=; \
rval=0; \ rval=0; \
./check_iolog_json $(srcdir)/regress/iolog_json/*.in || rval=`expr $$rval + $$?`; \
./check_iolog_path $(srcdir)/regress/iolog_path/data || rval=`expr $$rval + $$?`; \ ./check_iolog_path $(srcdir)/regress/iolog_path/data || rval=`expr $$rval + $$?`; \
./check_iolog_util || rval=`expr $$rval + $$?`; \ ./check_iolog_util || rval=`expr $$rval + $$?`; \
exit $$rval; \ exit $$rval; \
@@ -182,6 +188,20 @@ realclean: distclean
cleandir: realclean cleandir: realclean
# Autogenerated dependencies, do not modify # Autogenerated dependencies, do not modify
check_iolog_json.lo: $(srcdir)/regress/iolog_json/check_iolog_json.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_json.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(srcdir)/iolog_json.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_json/check_iolog_json.c
check_iolog_json.i: $(srcdir)/regress/iolog_json/check_iolog_json.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_json.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(srcdir)/iolog_json.h $(top_builddir)/config.h
$(CC) -E -o $@ $(CPPFLAGS) $<
check_iolog_json.plog: check_iolog_json.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/iolog_json/check_iolog_json.c --i-file $< --output-file $@
check_iolog_path.lo: $(srcdir)/regress/iolog_path/check_iolog_path.c \ check_iolog_path.lo: $(srcdir)/regress/iolog_path/check_iolog_path.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_iolog.h \
@@ -241,14 +261,14 @@ iolog_json.lo: $(srcdir)/iolog_json.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \ $(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h $(srcdir)/iolog_json.h $(top_builddir)/config.h
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_json.c $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/iolog_json.c
iolog_json.i: $(srcdir)/iolog_json.c $(incdir)/compat/stdbool.h \ iolog_json.i: $(srcdir)/iolog_json.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \ $(incdir)/sudo_iolog.h $(incdir)/sudo_json.h \
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
$(top_builddir)/config.h $(srcdir)/iolog_json.h $(top_builddir)/config.h
$(CC) -E -o $@ $(CPPFLAGS) $< $(CC) -E -o $@ $(CPPFLAGS) $<
iolog_json.plog: iolog_json.i iolog_json.plog: iolog_json.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_json.c --i-file $< --output-file $@ rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/iolog_json.c --i-file $< --output-file $@

View File

@@ -49,31 +49,10 @@
#include "sudo_compat.h" #include "sudo_compat.h"
#include "sudo_fatal.h" #include "sudo_fatal.h"
#include "sudo_debug.h" #include "sudo_debug.h"
#include "sudo_queue.h"
#include "sudo_json.h"
#include "sudo_util.h" #include "sudo_util.h"
#include "sudo_iolog.h" #include "sudo_iolog.h"
TAILQ_HEAD(json_item_list, json_item); #include "iolog_json.h"
struct json_object {
struct json_item *parent;
struct json_item_list items;
};
struct json_item {
TAILQ_ENTRY(json_item) entries;
char *name; /* may be NULL for first brace */
unsigned int lineno;
enum json_value_type type;
union {
struct json_object child;
char *string;
long long number;
id_t id;
bool boolean;
} u;
};
struct json_stack { struct json_stack {
unsigned int depth; unsigned int depth;
@@ -128,7 +107,7 @@ json_store_lines(struct json_item *item, struct iolog_info *li)
debug_return_bool(true); debug_return_bool(true);
} }
static char ** char **
json_array_to_strvec(struct json_object *array) json_array_to_strvec(struct json_object *array)
{ {
struct json_item *item; struct json_item *item;
@@ -137,6 +116,7 @@ json_array_to_strvec(struct json_object *array)
debug_decl(json_array_to_strvec, SUDO_DEBUG_UTIL); debug_decl(json_array_to_strvec, SUDO_DEBUG_UTIL);
TAILQ_FOREACH(item, &array->items, entries) { TAILQ_FOREACH(item, &array->items, entries) {
/* Can only convert arrays of string. */
if (item->type != JSON_STRING) { if (item->type != JSON_STRING) {
sudo_warnx(U_("expected JSON_STRING, got %d"), item->type); sudo_warnx(U_("expected JSON_STRING, got %d"), item->type);
debug_return_ptr(NULL); debug_return_ptr(NULL);
@@ -380,7 +360,7 @@ json_parse_string(char **strp)
debug_return_str(ret); debug_return_str(ret);
} }
static void void
free_json_items(struct json_item_list *items) free_json_items(struct json_item_list *items)
{ {
struct json_item *item; struct json_item *item;
@@ -498,6 +478,19 @@ json_insert_bool(struct json_item_list *items, char *name, bool value,
debug_return_bool(true); debug_return_bool(true);
} }
static bool
json_insert_null(struct json_item_list *items, char *name, unsigned int lineno)
{
struct json_item *item;
debug_decl(json_insert_null, SUDO_DEBUG_UTIL);
if ((item = new_json_item(JSON_NULL, name, lineno)) == NULL)
debug_return_bool(false);
TAILQ_INSERT_TAIL(items, item, entries);
debug_return_bool(true);
}
static bool static bool
json_insert_num(struct json_item_list *items, char *name, long long value, json_insert_num(struct json_item_list *items, char *name, long long value,
unsigned int lineno) unsigned int lineno)
@@ -557,10 +550,9 @@ json_stack_push(struct json_stack *stack, struct json_item_list *items,
} }
bool bool
iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li) iolog_parse_json(FILE *fp, const char *filename, struct json_object *root)
{ {
struct json_object root = { NULL, TAILQ_HEAD_INITIALIZER(root.items) }; struct json_object *curobj = root;
struct json_object *curobj = &root;
struct json_object *curarray = NULL; struct json_object *curarray = NULL;
struct json_stack objstack = JSON_STACK_INTIALIZER(objstack); struct json_stack objstack = JSON_STACK_INTIALIZER(objstack);
struct json_stack arrstack = JSON_STACK_INTIALIZER(arrstack); struct json_stack arrstack = JSON_STACK_INTIALIZER(arrstack);
@@ -569,9 +561,13 @@ iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li)
char *buf = NULL; char *buf = NULL;
size_t bufsize = 0; size_t bufsize = 0;
ssize_t len; ssize_t len;
long long num;
bool ret = false; bool ret = false;
debug_decl(iolog_parse_loginfo_json, SUDO_DEBUG_UTIL); long long num;
char ch;
debug_decl(iolog_parse_json, SUDO_DEBUG_UTIL);
root->parent = NULL;
TAILQ_INIT(&root->items);
while ((len = getdelim(&buf, &bufsize, '\n', fp)) != -1) { while ((len = getdelim(&buf, &bufsize, '\n', fp)) != -1) {
char *cp = buf; char *cp = buf;
@@ -625,8 +621,13 @@ iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li)
sudo_warnx(U_("unexpected array")); sudo_warnx(U_("unexpected array"));
goto parse_error; goto parse_error;
} }
curarray = json_stack_push(&arrstack, &curobj->items, curarray, if (curarray != NULL) {
JSON_ARRAY, name, lineno); curarray = json_stack_push(&arrstack, &curarray->items,
curarray, JSON_ARRAY, name, lineno);
} else {
curarray = json_stack_push(&arrstack, &curobj->items,
NULL, JSON_ARRAY, name, lineno);
}
if (curarray == NULL) if (curarray == NULL)
goto parse_error; goto parse_error;
name = NULL; name = NULL;
@@ -666,13 +667,15 @@ iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li)
} }
break; break;
case 't': case 't':
if (name == NULL) { if (name == NULL && curarray == NULL) {
sudo_warnx(U_("unexpected boolean")); sudo_warnx(U_("unexpected boolean"));
goto parse_error; goto parse_error;
} }
if (strcmp(cp, "true") != 0) if (strncmp(cp, "true", sizeof("true") - 1) != 0)
goto parse_error; goto parse_error;
cp += sizeof("true") - 1; cp += sizeof("true") - 1;
if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
goto parse_error;
if (curarray != NULL) { if (curarray != NULL) {
if (!json_insert_bool(&curarray->items, NULL, true, lineno)) if (!json_insert_bool(&curarray->items, NULL, true, lineno))
@@ -684,13 +687,15 @@ iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li)
} }
break; break;
case 'f': case 'f':
if (name == NULL) { if (name == NULL && curarray == NULL) {
sudo_warnx(U_("unexpected boolean")); sudo_warnx(U_("unexpected boolean"));
goto parse_error; goto parse_error;
} }
if (strcmp(cp, "false") != 0) if (strncmp(cp, "false", sizeof("false") - 1) != 0)
goto parse_error; goto parse_error;
cp += sizeof("false") - 1; cp += sizeof("false") - 1;
if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
goto parse_error;
if (curarray != NULL) { if (curarray != NULL) {
if (!json_insert_bool(&curarray->items, NULL, false, lineno)) if (!json_insert_bool(&curarray->items, NULL, false, lineno))
@@ -702,16 +707,34 @@ iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li)
} }
break; break;
case 'n': case 'n':
if (strcmp(cp, "null") == 0) if (name == NULL && curarray == NULL) {
sudo_warnx(U_("null not allowed")); sudo_warnx(U_("unexpected boolean"));
goto parse_error; goto parse_error;
}
if (strncmp(cp, "null", sizeof("null") - 1) != 0)
goto parse_error;
cp += sizeof("null") - 1;
if (*cp != ',' && !isspace((unsigned char)*cp) && *cp != '\0')
goto parse_error;
if (curarray != NULL) {
if (!json_insert_null(&curarray->items, NULL, lineno))
goto parse_error;
} else {
if (!json_insert_null(&curobj->items, name, lineno))
goto parse_error;
name = NULL;
}
break;
case '+': case '-': case '0': case '1': case '2': case '3': case '+': case '-': case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7': case '8': case '9': case '4': case '5': case '6': case '7': case '8': case '9':
if (name == NULL) { if (name == NULL && curarray == NULL) {
sudo_warnx(U_("unexpected number")); sudo_warnx(U_("unexpected number"));
goto parse_error; goto parse_error;
} }
len = strcspn(cp, " \t\r\n,"); /* XXX - strtonumx() would be simpler here. */
len = strcspn(cp, " \f\n\r\t\v,");
ch = cp[len];
cp[len] = '\0'; cp[len] = '\0';
num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr); num = sudo_strtonum(cp, LLONG_MIN, LLONG_MAX, &errstr);
if (errstr != NULL) { if (errstr != NULL) {
@@ -719,6 +742,7 @@ iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li)
goto parse_error; goto parse_error;
} }
cp += len; cp += len;
*cp = ch;
if (curarray != NULL) { if (curarray != NULL) {
if (!json_insert_num(&curarray->items, NULL, num, lineno)) if (!json_insert_num(&curarray->items, NULL, num, lineno))
@@ -743,18 +767,34 @@ iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li)
goto parse_error; goto parse_error;
} }
/* Walk the stack and parse entries. */ ret = true;
ret = iolog_parse_json_object(&root, li);
goto done; goto done;
parse_error: parse_error:
sudo_warnx(U_("%s/%s:%u unable to parse \"%s\""), iolog_dir, sudo_warnx(U_("%s:%u unable to parse \"%s\""), filename, lineno, buf);
"log.json", lineno, buf);
done: done:
free(buf); free(buf);
free(name); free(name);
free_json_items(&root.items); if (!ret)
free_json_items(&root->items);
debug_return_bool(ret);
}
bool
iolog_parse_loginfo_json(FILE *fp, const char *iolog_dir, struct iolog_info *li)
{
struct json_object root;
bool ret = false;
debug_decl(iolog_parse_loginfo_json, SUDO_DEBUG_UTIL);
if (iolog_parse_json(fp, iolog_dir, &root)) {
/* Walk the stack and parse entries. */
ret = iolog_parse_json_object(&root, li);
/* Cleanup. */
free_json_items(&root.items);
}
debug_return_bool(ret); debug_return_bool(ret);
} }

50
lib/iolog/iolog_json.h Normal file
View File

@@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and 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.
*/
#ifndef IOLOG_JSON_H
#define IOLOG_JSON_H
#include "sudo_json.h"
#include "sudo_queue.h"
TAILQ_HEAD(json_item_list, json_item);
struct json_object {
struct json_item *parent;
struct json_item_list items;
};
struct json_item {
TAILQ_ENTRY(json_item) entries;
char *name; /* may be NULL for first brace */
unsigned int lineno;
enum json_value_type type;
union {
struct json_object child;
char *string;
long long number;
id_t id;
bool boolean;
} u;
};
void free_json_items(struct json_item_list *items);
bool iolog_parse_json(FILE *fp, const char *filename, struct json_object *root);
char **json_array_to_strvec(struct json_object *array);
#endif /* IOLOG_JSON_H */

View File

@@ -0,0 +1,271 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2020 Todd C. Miller <Todd.Miller@sudo.ws>
*
* Permission to use, copy, modify, and 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 <config.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <limits.h>
#include <unistd.h>
#define SUDO_ERROR_WRAP 0
#include "sudo_compat.h"
#include "sudo_util.h"
#include "sudo_fatal.h"
#include "iolog_json.h"
__dso_public int main(int argc, char *argv[]);
bool
json_print_object(struct json_container *json, struct json_object *object)
{
struct json_item *item;
struct json_value json_value;
bool ret = false;
TAILQ_FOREACH(item, &object->items, entries) {
switch (item->type) {
case JSON_STRING:
json_value.type = JSON_STRING;
json_value.u.string = item->u.string;
if (!sudo_json_add_value(json, item->name, &json_value))
goto oom;
break;
case JSON_NUMBER:
json_value.type = JSON_NUMBER;
json_value.u.number = item->u.number;
if (!sudo_json_add_value(json, item->name, &json_value))
goto oom;
break;
case JSON_OBJECT:
if (!sudo_json_open_object(json, item->name))
goto oom;
if (!json_print_object(json, &item->u.child))
goto done;
if (!sudo_json_close_object(json))
goto oom;
break;
case JSON_ARRAY:
if (!sudo_json_open_array(json, item->name))
goto oom;
if (!json_print_object(json, &item->u.child))
goto done;
if (!sudo_json_close_array(json))
goto oom;
break;
case JSON_BOOL:
json_value.type = JSON_BOOL;
json_value.u.boolean = item->u.boolean;
if (!sudo_json_add_value(json, item->name, &json_value))
goto oom;
break;
case JSON_NULL:
json_value.type = JSON_NULL;
if (!sudo_json_add_value(json, item->name, &json_value))
goto oom;
break;
default:
sudo_warnx("unsupported JSON type %d", item->type);
goto done;
}
}
ret = true;
goto done;
oom:
sudo_warnx("%s: %s", __func__, "unable to allocate memory");
done:
return ret;
}
static bool
json_format(struct json_container *json, struct json_object *object)
{
struct json_item *item;
bool ret = false;
/* First object holds all the actual data. */
item = TAILQ_FIRST(&object->items);
if (item->type != JSON_OBJECT) {
sudo_warnx("expected JSON_OBJECT, got %d", item->type);
goto done;
}
object = &item->u.child;
if (!json_print_object(json, object))
goto done;
ret = true;
done:
return ret;
}
static void
usage(void)
{
fprintf(stderr, "usage: %s [-c] input_file ...\n",
getprogname());
exit(EXIT_FAILURE);
}
static bool
compare(FILE *fp, const char *infile, struct json_container *json)
{
const char *cp;
unsigned int lineno = 0;
size_t linesize = 0;
char *line = NULL;
ssize_t len;
cp = sudo_json_get_buf(json);
while ((len = getdelim(&line, &linesize, '\n', fp)) != -1) {
lineno++;
/* skip open/close brace, not present in formatted output */
if (lineno == 1 && strcmp(line, "{\n") == 0)
continue;
if (*cp == '\0' && strcmp(line, "}\n") == 0)
continue;
/* Ignore newlines in output to make comparison easier. */
if (*cp == '\n')
cp++;
if (line[len - 1] == '\n')
len--;
if (strncmp(line, cp, len) != 0) {
fprintf(stderr, "%s: mismatch on line %u\n", infile, lineno);
fprintf(stderr, "expected: %s", line);
fprintf(stderr, "got : %.*s\n", (int)len, cp);
return false;
}
cp += len;
}
free(line);
return true;
}
int
main(int argc, char *argv[])
{
struct json_object root;
int ch, i, tests = 0, errors = 0;
bool cat = false;
initprogname(argc > 0 ? argv[0] : "check_iolog_json");
while ((ch = getopt(argc, argv, "c")) != -1) {
switch (ch) {
case 'c':
cat = true;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc < 1)
usage();
for (i = 0; i < argc; i++) {
struct json_container json;
const char *infile = argv[i];
const char *outfile = argv[i];
const char *cp;
char pathbuf[PATH_MAX];
FILE *infp = NULL;
FILE *outfp = NULL;
tests++;
if (!sudo_json_init(&json, 4, false, true)) {
errors++;
continue;
}
/* Parse input file. */
if ((infp = fopen(infile, "r")) == NULL) {
sudo_warn("%s", argv[1]);
errors++;
goto next;
}
if (!iolog_parse_json(infp, infile, &root)) {
errors++;
goto next;
}
/* Format as pretty-printed JSON */
if (!json_format(&json, &root)) {
errors++;
goto next;
}
/* Check for a .out.ok file in the same location as the .in file. */
cp = strrchr(infile, '.');
if (cp != NULL && strcmp(cp, ".in") == 0) {
snprintf(pathbuf, sizeof(pathbuf), "%.*s.out.ok",
(int)(cp - infile), infile);
if ((outfp = fopen(pathbuf, "r")) != NULL)
outfile = pathbuf;
}
if (outfp == NULL)
outfp = infp;
/* Compare output to expected output. */
rewind(outfp);
if (!compare(outfp, outfile, &json))
errors++;
/* Write the formatted output to stdout for -c (cat) */
if (cat) {
fprintf(stdout, "{%s\n}\n", sudo_json_get_buf(&json));
fflush(stdout);
}
next:
free_json_items(&root.items);
sudo_json_free(&json);
if (infp != NULL)
fclose(infp);
if (outfp != NULL && outfp != infp)
fclose(outfp);
}
if (tests != 0) {
printf("iolog_json: %d test%s run, %d errors, %d%% success rate\n",
tests, tests == 1 ? "" : "s", errors,
(tests - errors) * 100 / tests);
}
exit(errors);
}

View File

@@ -0,0 +1,34 @@
{
"timestamp": {
"seconds": 1584993067,
"nanoseconds": 880288287
},
"columns": 80,
"command": "/usr/bin/make",
"lines": 24,
"runargv": [
"make",
"test"
],
"runenv": [
"LANG=en_US.UTF-8",
"PATH=/bin:/sbin:/usr/games:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin",
"TERM=vt100",
"MAIL=/var/mail/root",
"LOGNAME=root",
"USER=root",
"HOME=/root",
"SHELL=/bin/ksh",
"SUDO_COMMAND=/usr/bin/make test",
"SUDO_USER=millert",
"SUDO_UID=8036",
"SUDO_GID=20",
"A__z=\"*SHLVL"
],
"runuid": 0,
"runuser": "root",
"submitcwd": "/home/test",
"submithost": "sudo.ws",
"submituser": "millert",
"ttyname": "/dev/console"
}

View File

@@ -0,0 +1,28 @@
{
"timestamp": { "seconds": 1584993067, "nanoseconds": 880288287 },
"columns": 80,
"command": "/usr/bin/make",
"lines": 24,
"runargv": [ "make", "test" ],
"runenv": [
"LANG=en_US.UTF-8",
"PATH=/bin:/sbin:/usr/games:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin",
"TERM=vt100",
"MAIL=/var/mail/root",
"LOGNAME=root",
"USER=root",
"HOME=/root",
"SHELL=/bin/ksh",
"SUDO_COMMAND=/usr/bin/make test",
"SUDO_USER=millert",
"SUDO_UID=8036",
"SUDO_GID=20",
"A__z=\"*SHLVL"
],
"runuid": 0,
"runuser": "root",
"submitcwd": "/home/test",
"submithost": "sudo.ws",
"submituser": "millert",
"ttyname": "/dev/console"
}

View File

@@ -0,0 +1,34 @@
{
"timestamp": {
"seconds": 1584993067,
"nanoseconds": 880288287
},
"columns": 80,
"command": "/usr/bin/make",
"lines": 24,
"runargv": [
"make",
"test"
],
"runenv": [
"LANG=en_US.UTF-8",
"PATH=/bin:/sbin:/usr/games:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin",
"TERM=vt100",
"MAIL=/var/mail/root",
"LOGNAME=root",
"USER=root",
"HOME=/root",
"SHELL=/bin/ksh",
"SUDO_COMMAND=/usr/bin/make test",
"SUDO_USER=millert",
"SUDO_UID=8036",
"SUDO_GID=20",
"A__z=\"*SHLVL"
],
"runuid": 0,
"runuser": "root",
"submitcwd": "/home/test",
"submithost": "sudo.ws",
"submituser": "millert",
"ttyname": "/dev/console"
}

View File

@@ -0,0 +1,22 @@
{
"true": false,
"false": true,
"number": 1234567890,
"null": null,
"string": "nonsense",
"scope": {
"a": "b",
"bah": null
},
"array1": [
"foo",
"bar",
[
123,
null,
false,
"fizz",
"buzz"
]
]
}

View File

@@ -274,9 +274,14 @@ sudo_json_open_array_v1(struct json_container *json, const char *name)
json_append_indent(json, json->indent_level); json_append_indent(json, json->indent_level);
if (name != NULL) {
json_append_string(json, name); json_append_string(json, name);
if (!json_append_buf(json, ": [")) if (!json_append_buf(json, ": ["))
debug_return_bool(false); debug_return_bool(false);
} else {
if (!json_append_buf(json, "["))
debug_return_bool(false);
}
json->indent_level += json->indent_increment; json->indent_level += json->indent_increment;
json->need_comma = false; json->need_comma = false;
@@ -300,7 +305,7 @@ sudo_json_close_array_v1(struct json_container *json)
debug_return_bool(true); debug_return_bool(true);
} }
bool static bool
sudo_json_add_value_int(struct json_container *json, const char *name, sudo_json_add_value_int(struct json_container *json, const char *name,
struct json_value *value, bool as_object) struct json_value *value, bool as_object)
{ {