All of lore.kernel.org
 help / color / mirror / Atom feed
From: Stanislav Fomichev <stfomichev@gmail.com>
To: Jakub Kicinski <kuba@kernel.org>
Cc: davem@davemloft.net, donald.hunter@gmail.com,
	netdev@vger.kernel.org, edumazet@google.com, pabeni@redhat.com,
	andrew+netdev@lunn.ch, horms@kernel.org, sdf@fomichev.me,
	joe@dama.to, jstancek@redhat.com
Subject: Re: [PATCH net-next 2/5] tools: ynltool: create skeleton for the C command
Date: Thu, 6 Nov 2025 08:37:07 -0800	[thread overview]
Message-ID: <aQzOs-0tFbGJOwgL@mini-arch> (raw)
In-Reply-To: <20251104232348.1954349-3-kuba@kernel.org>

On 11/04, Jakub Kicinski wrote:
> Based on past discussions it seems like integration of YNL into
> iproute2 is unlikely. YNL itself is not great as a C library,
> since it has no backward compat (we routinely change types).
> 
> Most of the operations can be performed with the generic Python
> CLI directly. There is, however, a handful of operations where
> summarization of kernel output is very useful (mostly related
> to stats: page-pool, qstat).
> 
> Create a command (inspired by bpftool, I think it stood the test
> of time reasonably well) to be able to plug the subcommands into.
> 
> Link: https://lore.kernel.org/1754895902-8790-1-git-send-email-ernis@linux.microsoft.com
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
>  tools/net/ynl/Makefile              |   3 +-
>  tools/net/ynl/ynltool/Makefile      |  44 +++++
>  tools/net/ynl/ynltool/json_writer.h |  75 ++++++++
>  tools/net/ynl/ynltool/main.h        |  62 ++++++
>  tools/net/ynl/ynltool/json_writer.c | 288 ++++++++++++++++++++++++++++
>  tools/net/ynl/ynltool/main.c        | 240 +++++++++++++++++++++++
>  tools/net/ynl/ynltool/.gitignore    |   1 +
>  7 files changed, 712 insertions(+), 1 deletion(-)
>  create mode 100644 tools/net/ynl/ynltool/Makefile
>  create mode 100644 tools/net/ynl/ynltool/json_writer.h
>  create mode 100644 tools/net/ynl/ynltool/main.h
>  create mode 100644 tools/net/ynl/ynltool/json_writer.c
>  create mode 100644 tools/net/ynl/ynltool/main.c
>  create mode 100644 tools/net/ynl/ynltool/.gitignore
> 
> diff --git a/tools/net/ynl/Makefile b/tools/net/ynl/Makefile
> index 211df5a93ad9..31ed20c0f3f8 100644
> --- a/tools/net/ynl/Makefile
> +++ b/tools/net/ynl/Makefile
> @@ -12,10 +12,11 @@ endif
>  libdir  ?= $(prefix)/$(libdir_relative)
>  includedir ?= $(prefix)/include
>  
> -SUBDIRS = lib generated samples
> +SUBDIRS = lib generated samples ynltool
>  
>  all: $(SUBDIRS) libynl.a
>  
> +ynltool: | lib generated libynl.a
>  samples: | lib generated
>  libynl.a: | lib generated
>  	@echo -e "\tAR $@"
> diff --git a/tools/net/ynl/ynltool/Makefile b/tools/net/ynl/ynltool/Makefile
> new file mode 100644
> index 000000000000..ce27dc691ffe
> --- /dev/null
> +++ b/tools/net/ynl/ynltool/Makefile
> @@ -0,0 +1,44 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +include ../Makefile.deps
> +
> +INSTALL	?= install
> +prefix  ?= /usr
> +
> +CC := gcc
> +CFLAGS := -Wall -Wextra -Werror -O2
> +ifeq ("$(DEBUG)","1")
> +  CFLAGS += -g -fsanitize=address -fsanitize=leak -static-libasan
> +endif
> +CFLAGS += -I../lib
> +
> +SRCS := $(wildcard *.c)
> +OBJS := $(patsubst %.c,$(OUTPUT)%.o,$(SRCS))
> +
> +YNLTOOL := $(OUTPUT)ynltool
> +
> +include $(wildcard *.d)
> +
> +all: $(YNLTOOL)
> +
> +$(YNLTOOL): $(OBJS)
> +	@echo -e "\tLINK $@"
> +	@$(CC) $(CFLAGS) -o $@ $(OBJS)
> +
> +%.o: %.c main.h json_writer.h
> +	@echo -e "\tCC $@"
> +	@$(COMPILE.c) -MMD -c -o $@ $<
> +
> +clean:
> +	rm -f *.o *.d *~
> +
> +distclean: clean
> +	rm -f $(YNLTOOL)
> +
> +bindir ?= /usr/bin
> +
> +install: $(YNLTOOL)
> +	install -m 0755 $(YNLTOOL) $(DESTDIR)$(bindir)/$(YNLTOOL)
> +
> +.PHONY: all clean distclean
> +.DEFAULT_GOAL=all
> diff --git a/tools/net/ynl/ynltool/json_writer.h b/tools/net/ynl/ynltool/json_writer.h
> new file mode 100644
> index 000000000000..0f1e63c88f6a
> --- /dev/null
> +++ b/tools/net/ynl/ynltool/json_writer.h
> @@ -0,0 +1,75 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/*
> + * Simple streaming JSON writer
> + *
> + * This takes care of the annoying bits of JSON syntax like the commas
> + * after elements
> + *
> + * Authors:	Stephen Hemminger <stephen@networkplumber.org>
> + */
> +
> +#ifndef _JSON_WRITER_H_
> +#define _JSON_WRITER_H_
> +
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <stdarg.h>
> +#include <stdio.h>
> +
> +/* Opaque class structure */
> +typedef struct json_writer json_writer_t;
> +
> +/* Create a new JSON stream */
> +json_writer_t *jsonw_new(FILE *f);
> +/* End output to JSON stream */
> +void jsonw_destroy(json_writer_t **self_p);
> +
> +/* Cause output to have pretty whitespace */
> +void jsonw_pretty(json_writer_t *self, bool on);
> +
> +/* Reset separator to create new JSON */
> +void jsonw_reset(json_writer_t *self);
> +
> +/* Add property name */
> +void jsonw_name(json_writer_t *self, const char *name);
> +
> +/* Add value  */
> +void __attribute__((format(printf, 2, 0))) jsonw_vprintf_enquote(json_writer_t *self,
> +								 const char *fmt,
> +								 va_list ap);
> +void __attribute__((format(printf, 2, 3))) jsonw_printf(json_writer_t *self,
> +							const char *fmt, ...);
> +void jsonw_string(json_writer_t *self, const char *value);
> +void jsonw_bool(json_writer_t *self, bool value);
> +void jsonw_float(json_writer_t *self, double number);
> +void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num);
> +void jsonw_uint(json_writer_t *self, uint64_t number);
> +void jsonw_hu(json_writer_t *self, unsigned short number);
> +void jsonw_int(json_writer_t *self, int64_t number);
> +void jsonw_null(json_writer_t *self);
> +void jsonw_lluint(json_writer_t *self, unsigned long long int num);
> +
> +/* Useful Combinations of name and value */
> +void jsonw_string_field(json_writer_t *self, const char *prop, const char *val);
> +void jsonw_bool_field(json_writer_t *self, const char *prop, bool value);
> +void jsonw_float_field(json_writer_t *self, const char *prop, double num);
> +void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num);
> +void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num);
> +void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num);
> +void jsonw_null_field(json_writer_t *self, const char *prop);
> +void jsonw_lluint_field(json_writer_t *self, const char *prop,
> +			unsigned long long int num);
> +void jsonw_float_field_fmt(json_writer_t *self, const char *prop,
> +			   const char *fmt, double val);
> +
> +/* Collections */
> +void jsonw_start_object(json_writer_t *self);
> +void jsonw_end_object(json_writer_t *self);
> +
> +void jsonw_start_array(json_writer_t *self);
> +void jsonw_end_array(json_writer_t *self);
> +
> +/* Override default exception handling */
> +typedef void (jsonw_err_handler_fn)(const char *);
> +
> +#endif /* _JSON_WRITER_H_ */
> diff --git a/tools/net/ynl/ynltool/main.h b/tools/net/ynl/ynltool/main.h
> new file mode 100644
> index 000000000000..f4a70acf2085
> --- /dev/null
> +++ b/tools/net/ynl/ynltool/main.h
> @@ -0,0 +1,62 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/* Copyright (C) 2017-2018 Netronome Systems, Inc. */
> +/* Copyright Meta Platforms, Inc. and affiliates */
> +
> +#ifndef __YNLTOOL_H
> +#define __YNLTOOL_H
> +
> +#ifndef _GNU_SOURCE
> +#define _GNU_SOURCE
> +#endif
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <errno.h>
> +#include <string.h>
> +
> +#include "json_writer.h"
> +
> +#define NEXT_ARG()	({ argc--; argv++; if (argc < 0) usage(); })
> +#define NEXT_ARGP()	({ (*argc)--; (*argv)++; if (*argc < 0) usage(); })
> +#define BAD_ARG()	({ p_err("what is '%s'?", *argv); -1; })
> +#define GET_ARG()	({ argc--; *argv++; })
> +#define REQ_ARGS(cnt)							\
> +	({								\
> +		int _cnt = (cnt);					\
> +		bool _res;						\
> +									\
> +		if (argc < _cnt) {					\
> +			p_err("'%s' needs at least %d arguments, %d found", \
> +			      argv[-1], _cnt, argc);			\
> +			_res = false;					\
> +		} else {						\
> +			_res = true;					\
> +		}							\
> +		_res;							\
> +	})
> +
> +#define HELP_SPEC_OPTIONS						\
> +	"OPTIONS := { {-j|--json} [{-p|--pretty}] }"
> +
> +extern const char *bin_name;
> +
> +extern json_writer_t *json_wtr;
> +extern bool json_output;
> +extern bool pretty_output;
> +
> +void __attribute__((format(printf, 1, 2))) p_err(const char *fmt, ...);
> +void __attribute__((format(printf, 1, 2))) p_info(const char *fmt, ...);
> +
> +bool is_prefix(const char *pfx, const char *str);
> +int detect_common_prefix(const char *arg, ...);
> +void usage(void) __attribute__((noreturn));
> +
> +struct cmd {
> +	const char *cmd;
> +	int (*func)(int argc, char **argv);
> +};
> +
> +int cmd_select(const struct cmd *cmds, int argc, char **argv,
> +	       int (*help)(int argc, char **argv));
> +
> +#endif /* __YNLTOOL_H */
> diff --git a/tools/net/ynl/ynltool/json_writer.c b/tools/net/ynl/ynltool/json_writer.c
> new file mode 100644
> index 000000000000..c8685e592cd3
> --- /dev/null
> +++ b/tools/net/ynl/ynltool/json_writer.c
> @@ -0,0 +1,288 @@
> +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
> +/*
> + * Simple streaming JSON writer
> + *
> + * This takes care of the annoying bits of JSON syntax like the commas
> + * after elements
> + *
> + * Authors:	Stephen Hemminger <stephen@networkplumber.org>
> + */
> +
> +#include <stdio.h>
> +#include <stdbool.h>
> +#include <stdarg.h>
> +#include <assert.h>
> +#include <malloc.h>
> +#include <inttypes.h>
> +#include <stdint.h>
> +
> +#include "json_writer.h"
> +
> +struct json_writer {
> +	FILE		*out;
> +	unsigned	depth;
> +	bool		pretty;
> +	char		sep;
> +};
> +
> +static void jsonw_indent(json_writer_t *self)
> +{
> +	unsigned i;
> +	for (i = 0; i < self->depth; ++i)
> +		fputs("    ", self->out);
> +}
> +
> +static void jsonw_eol(json_writer_t *self)
> +{
> +	if (!self->pretty)
> +		return;
> +
> +	putc('\n', self->out);
> +	jsonw_indent(self);
> +}
> +
> +static void jsonw_eor(json_writer_t *self)
> +{
> +	if (self->sep != '\0')
> +		putc(self->sep, self->out);
> +	self->sep = ',';
> +}
> +
> +static void jsonw_puts(json_writer_t *self, const char *str)
> +{
> +	putc('"', self->out);
> +	for (; *str; ++str)
> +		switch (*str) {
> +		case '\t':
> +			fputs("\\t", self->out);
> +			break;
> +		case '\n':
> +			fputs("\\n", self->out);
> +			break;
> +		case '\r':
> +			fputs("\\r", self->out);
> +			break;
> +		case '\f':
> +			fputs("\\f", self->out);
> +			break;
> +		case '\b':
> +			fputs("\\b", self->out);
> +			break;
> +		case '\\':
> +			fputs("\\\\", self->out);
> +			break;
> +		case '"':
> +			fputs("\\\"", self->out);
> +			break;
> +		default:
> +			putc(*str, self->out);
> +		}
> +	putc('"', self->out);
> +}
> +
> +json_writer_t *jsonw_new(FILE *f)
> +{
> +	json_writer_t *self = malloc(sizeof(*self));
> +	if (self) {
> +		self->out = f;
> +		self->depth = 0;
> +		self->pretty = false;
> +		self->sep = '\0';
> +	}
> +	return self;
> +}
> +
> +void jsonw_destroy(json_writer_t **self_p)
> +{
> +	json_writer_t *self = *self_p;
> +
> +	assert(self->depth == 0);
> +	fputs("\n", self->out);
> +	fflush(self->out);
> +	free(self);
> +	*self_p = NULL;
> +}
> +
> +void jsonw_pretty(json_writer_t *self, bool on)
> +{
> +	self->pretty = on;
> +}
> +
> +void jsonw_reset(json_writer_t *self)
> +{
> +	assert(self->depth == 0);
> +	self->sep = '\0';
> +}
> +
> +static void jsonw_begin(json_writer_t *self, int c)
> +{
> +	jsonw_eor(self);
> +	putc(c, self->out);
> +	++self->depth;
> +	self->sep = '\0';
> +}
> +
> +static void jsonw_end(json_writer_t *self, int c)
> +{
> +	assert(self->depth > 0);
> +
> +	--self->depth;
> +	if (self->sep != '\0')
> +		jsonw_eol(self);
> +	putc(c, self->out);
> +	self->sep = ',';
> +}
> +
> +void jsonw_name(json_writer_t *self, const char *name)
> +{
> +	jsonw_eor(self);
> +	jsonw_eol(self);
> +	self->sep = '\0';
> +	jsonw_puts(self, name);
> +	putc(':', self->out);
> +	if (self->pretty)
> +		putc(' ', self->out);
> +}
> +
> +void jsonw_vprintf_enquote(json_writer_t *self, const char *fmt, va_list ap)
> +{
> +	jsonw_eor(self);
> +	putc('"', self->out);
> +	vfprintf(self->out, fmt, ap);
> +	putc('"', self->out);
> +}
> +
> +void jsonw_printf(json_writer_t *self, const char *fmt, ...)
> +{
> +	va_list ap;
> +
> +	va_start(ap, fmt);
> +	jsonw_eor(self);
> +	vfprintf(self->out, fmt, ap);
> +	va_end(ap);
> +}
> +
> +void jsonw_start_object(json_writer_t *self)
> +{
> +	jsonw_begin(self, '{');
> +}
> +
> +void jsonw_end_object(json_writer_t *self)
> +{
> +	jsonw_end(self, '}');
> +}
> +
> +void jsonw_start_array(json_writer_t *self)
> +{
> +	jsonw_begin(self, '[');
> +}
> +
> +void jsonw_end_array(json_writer_t *self)
> +{
> +	jsonw_end(self, ']');
> +}
> +
> +void jsonw_string(json_writer_t *self, const char *value)
> +{
> +	jsonw_eor(self);
> +	jsonw_puts(self, value);
> +}
> +
> +void jsonw_bool(json_writer_t *self, bool val)
> +{
> +	jsonw_printf(self, "%s", val ? "true" : "false");
> +}
> +
> +void jsonw_null(json_writer_t *self)
> +{
> +	jsonw_printf(self, "null");
> +}
> +
> +void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num)
> +{
> +	jsonw_printf(self, fmt, num);
> +}
> +
> +void jsonw_float(json_writer_t *self, double num)
> +{
> +	jsonw_printf(self, "%g", num);
> +}
> +
> +void jsonw_hu(json_writer_t *self, unsigned short num)
> +{
> +	jsonw_printf(self, "%hu", num);
> +}
> +
> +void jsonw_uint(json_writer_t *self, uint64_t num)
> +{
> +	jsonw_printf(self, "%"PRIu64, num);
> +}
> +
> +void jsonw_lluint(json_writer_t *self, unsigned long long int num)
> +{
> +	jsonw_printf(self, "%llu", num);
> +}
> +
> +void jsonw_int(json_writer_t *self, int64_t num)
> +{
> +	jsonw_printf(self, "%"PRId64, num);
> +}
> +
> +void jsonw_string_field(json_writer_t *self, const char *prop, const char *val)
> +{
> +	jsonw_name(self, prop);
> +	jsonw_string(self, val);
> +}
> +
> +void jsonw_bool_field(json_writer_t *self, const char *prop, bool val)
> +{
> +	jsonw_name(self, prop);
> +	jsonw_bool(self, val);
> +}
> +
> +void jsonw_float_field(json_writer_t *self, const char *prop, double val)
> +{
> +	jsonw_name(self, prop);
> +	jsonw_float(self, val);
> +}
> +
> +void jsonw_float_field_fmt(json_writer_t *self,
> +			   const char *prop,
> +			   const char *fmt,
> +			   double val)
> +{
> +	jsonw_name(self, prop);
> +	jsonw_float_fmt(self, fmt, val);
> +}
> +
> +void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num)
> +{
> +	jsonw_name(self, prop);
> +	jsonw_uint(self, num);
> +}
> +
> +void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num)
> +{
> +	jsonw_name(self, prop);
> +	jsonw_hu(self, num);
> +}
> +
> +void jsonw_lluint_field(json_writer_t *self,
> +			const char *prop,
> +			unsigned long long int num)
> +{
> +	jsonw_name(self, prop);
> +	jsonw_lluint(self, num);
> +}
> +
> +void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num)
> +{
> +	jsonw_name(self, prop);
> +	jsonw_int(self, num);
> +}
> +
> +void jsonw_null_field(json_writer_t *self, const char *prop)
> +{
> +	jsonw_name(self, prop);
> +	jsonw_null(self);
> +}
> diff --git a/tools/net/ynl/ynltool/main.c b/tools/net/ynl/ynltool/main.c
> new file mode 100644
> index 000000000000..c5047fad50cf
> --- /dev/null
> +++ b/tools/net/ynl/ynltool/main.c
> @@ -0,0 +1,240 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +/* Copyright (C) 2017-2018 Netronome Systems, Inc. */
> +/* Copyright Meta Platforms, Inc. and affiliates */
> +
> +#include <ctype.h>
> +#include <errno.h>
> +#include <getopt.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <stdarg.h>
> +
> +#include "main.h"
> +
> +const char *bin_name;
> +static int last_argc;
> +static char **last_argv;
> +static int (*last_do_help)(int argc, char **argv);
> +json_writer_t *json_wtr;
> +bool pretty_output;
> +bool json_output;
> +
> +static void __attribute__((noreturn)) clean_and_exit(int i)
> +{
> +	if (json_output)
> +		jsonw_destroy(&json_wtr);
> +
> +	exit(i);
> +}
> +
> +void usage(void)
> +{
> +	last_do_help(last_argc - 1, last_argv + 1);
> +
> +	clean_and_exit(-1);
> +}
> +
> +static int do_help(int argc __attribute__((unused)),
> +		   char **argv __attribute__((unused)))
> +{
> +	if (json_output) {
> +		jsonw_null(json_wtr);
> +		return 0;
> +	}
> +
> +	fprintf(stderr,
> +		"Usage: %s [OPTIONS] OBJECT { COMMAND | help }\n"
> +		"       %s version\n"
> +		"\n"
> +		"       OBJECT := { }\n"
> +		"       " HELP_SPEC_OPTIONS "\n"
> +		"",
> +		bin_name, bin_name);
> +
> +	return 0;
> +}
> +
> +static int do_version(int argc __attribute__((unused)),
> +		      char **argv __attribute__((unused)))
> +{
> +	if (json_output) {
> +		jsonw_start_object(json_wtr);
> +		jsonw_name(json_wtr, "version");
> +		jsonw_printf(json_wtr, "\"0.1.0\"");

Any reason not to start with something like commit 4bfe3bd3cc35
("tools/bpftool: use version from the kernel source tree") here?
Otherwise I doubt 0.1.0 will be changed ever.

  reply	other threads:[~2025-11-06 16:37 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-04 23:23 [PATCH net-next 0/5] tools: ynl: turn the page-pool sample into a real tool Jakub Kicinski
2025-11-04 23:23 ` [PATCH net-next 1/5] netlink: specs: netdev add missing stats to qstat-get Jakub Kicinski
2025-11-04 23:23 ` [PATCH net-next 2/5] tools: ynltool: create skeleton for the C command Jakub Kicinski
2025-11-06 16:37   ` Stanislav Fomichev [this message]
2025-11-06 22:24     ` Jakub Kicinski
2025-11-04 23:23 ` [PATCH net-next 3/5] tools: ynltool: add page-pool stats Jakub Kicinski
2025-11-04 23:23 ` [PATCH net-next 4/5] tools: ynltool: add qstats support Jakub Kicinski
2025-11-04 23:23 ` [PATCH net-next 5/5] tools: ynltool: add traffic distribution balance Jakub Kicinski
2025-11-06 15:02 ` [PATCH net-next 0/5] tools: ynl: turn the page-pool sample into a real tool Jakub Kicinski
2025-11-06 16:34   ` Stanislav Fomichev
2025-11-07 16:10 ` patchwork-bot+netdevbpf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aQzOs-0tFbGJOwgL@mini-arch \
    --to=stfomichev@gmail.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=donald.hunter@gmail.com \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=joe@dama.to \
    --cc=jstancek@redhat.com \
    --cc=kuba@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sdf@fomichev.me \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.